TimeUnit
这是一个完整的 TimeUnit 枚举类源码。它是 Java 并发编程中的基础设施,用类型安全的方式来表示时间单位,并提供跨单位转换、超时控制、线程等待/睡眠等工具方法。
下面我们逐层分解这段代码,解释它的设计思路和每个部分的作用。
一、类的作用与设计理念(类注释)
/**
* A {@code TimeUnit} represents time durations at a given unit of
* granularity and provides utility methods to convert across units,
* and to perform timing and delay operations in these units.
...
*/
public enum TimeUnit { ... }
TimeUnit 是一个枚举,它的核心作用有三个:
- 表示时间粒度:纳秒、微秒、毫秒、秒、分、时、天。
- 在不同单位间转换数值(如 10 分钟是多少毫秒)。
- 提供基于这些单位的定时等待操作(
wait、sleep、join)。
注释中强调了几个关键点:
TimeUnit不保存时间值本身,它只是“单位标尺”,帮助我们解释一个数值的含义。- 例如
lock.tryLock(50L, TimeUnit.MILLISECONDS)表示 50 毫秒,而同样的50L, TimeUnit.SECONDS就表示 50 秒。 - 实际等待精度不一定能到达纳秒级,具体由底层实现决定。
二、枚举常量与基准比例常量
NANOSECONDS(TimeUnit.NANO_SCALE),
MICROSECONDS(TimeUnit.MICRO_SCALE),
MILLISECONDS(TimeUnit.MILLI_SCALE),
SECONDS(TimeUnit.SECOND_SCALE),
MINUTES(TimeUnit.MINUTE_SCALE),
HOURS(TimeUnit.HOUR_SCALE),
DAYS(TimeUnit.DAY_SCALE);
枚举实例分别代表 7 种时间单位。每个实例在构造时传入一个比例因子 scale,表示该单位包含多少纳秒。
比例常量定义如下:
private static final long NANO_SCALE = 1L;
private static final long MICRO_SCALE = 1000L * NANO_SCALE; // 1_000
private static final long MILLI_SCALE = 1000L * MICRO_SCALE; // 1_000_000
private static final long SECOND_SCALE = 1000L * MILLI_SCALE; // 1_000_000_000
private static final long MINUTE_SCALE = 60L * SECOND_SCALE;
private static final long HOUR_SCALE = 60L * MINUTE_SCALE;
private static final long DAY_SCALE = 24L * HOUR_SCALE;
这样,所有比例都以纳秒为最小基准,转换时可以统一用纳秒计算。
三、预计算的转换缓存字段
private final long scale;
private final long maxNanos;
private final long maxMicros;
private final long maxMillis;
private final long maxSecs;
private final long microRatio;
private final int milliRatio;
private final int secRatio;
字段含义
scale– 该单位对应的纳秒数(构造参数s)。maxNanos– 该单位可安全转为纳秒的最大值:Long.MAX_VALUE / scale。
如果传入值d > maxNanos,则d * scale会溢出,此时应饱和到Long.MAX_VALUE。microRatio– 该单位与微秒之间的转换比率(若s >= MICRO_SCALE,则是s / MICRO_SCALE;否则是MICRO_SCALE / s,用于向下除法)。maxMicros– 该单位转换到微秒的安全上界。milliRatio– 与毫秒的转换比率(同样是s / MILLI_SCALE或MILLI_SCALE / s),存为int,因为比率不会超过int范围。maxMillis– 转换到毫秒的安全上界。secRatio– 与秒的转换比率(存为int)。maxSecs– 转换到秒的安全上界。
为什么只缓存到秒?
源码注释:
/*
* Instances cache conversion ratios and saturation cutoffs for
* the units up through SECONDS. Other cases compute them, in
* method cvt.
*/
秒及以下单位是最常用的(sleep、wait、join 都用到毫秒/纳秒),缓存这些比率可以加速 toNanos、toMicros、toMillis、toSeconds 等方法。对于分、时、天,转换频率低,直接调用 cvt 通用方法即可。
四、构造方法 TimeUnit(long s)
private TimeUnit(long s) {
this.scale = s;
this.maxNanos = Long.MAX_VALUE / s;
long ur = (s >= MICRO_SCALE) ? (s / MICRO_SCALE) : (MICRO_SCALE / s);
this.microRatio = ur;
this.maxMicros = Long.MAX_VALUE / ur;
long mr = (s >= MILLI_SCALE) ? (s / MILLI_SCALE) : (MILLI_SCALE / s);
this.milliRatio = (int)mr;
this.maxMillis = Long.MAX_VALUE / mr;
long sr = (s >= SECOND_SCALE) ? (s / SECOND_SCALE) : (SECOND_SCALE / s);
this.secRatio = (int)sr;
this.maxSecs = Long.MAX_VALUE / sr;
}
- 传入
s就是该单位的scale(纳秒数)。 - 计算各个 ratio 时,要根据当前单位是大于还是小于目标单位来决定用法:
- 如果当前单位 ≥ 目标单位,
ratio = scale / 目标scale(1 个当前单位 = 多少个目标单位)。 - 如果当前单位 < 目标单位,
ratio = 目标scale / scale(需用除法才能转为更大单位)。
- 如果当前单位 ≥ 目标单位,
- 同时根据
Long.MAX_VALUE / ratio算出安全上界,避免乘法溢出。
这些值在枚举实例初始化时就固定好了,后续方法可以零开销地直接使用。
五、通用转换方法 cvt(long d, long dst, long src)
private static long cvt(long d, long dst, long src) {
long r, m;
if (src == dst)
return d;
else if (src < dst)
return d / (dst / src);
else if (d > (m = Long.MAX_VALUE / (r = src / dst)))
return Long.MAX_VALUE;
else if (d < -m)
return Long.MIN_VALUE;
else
return d * r;
}
这是最基础的转换逻辑:
dst– 目标单位的scale(纳秒值)src– 源单位的scale- 如果
src == dst,直接返回。 - 如果
src < dst(源单位比目标单位更精细),则需要除法:d / (dst / src)(先将目标转成源单位的倍数,再除)。这样d除以一个整数,向下取整。 - 如果
src > dst(源单位比目标单位更大),则需要乘法:先算r = src / dst,然后检查d * r是否会溢出。Long.MAX_VALUE / r算出安全边界m,如果d > m正向溢出返回Long.MAX_VALUE;如果d < -m负向溢出返回Long.MIN_VALUE;否则安全返回d * r。
这种方法保证了任何转换都不会抛出异常,而是采用饱和策略。
六、convert 方法(长整型版本)
public long convert(long sourceDuration, TimeUnit sourceUnit) {
switch (this) {
case NANOSECONDS: return sourceUnit.toNanos(sourceDuration);
case MICROSECONDS: return sourceUnit.toMicros(sourceDuration);
case MILLISECONDS: return sourceUnit.toMillis(sourceDuration);
case SECONDS: return sourceUnit.toSeconds(sourceDuration);
default: return cvt(sourceDuration, scale, sourceUnit.scale);
}
}
- 根据目标单位(即
this,当前枚举实例)进行 switch。 - 对于纳秒、微秒、毫秒、秒这四种常用单位,直接委托给对应的
toXxx方法,它们内部有预计算的缓存,效率更高。 - 对于分、时、天等较大单位,退而使用
cvt通用方法。
七、convert(Duration duration) (Java 11 新增)
public long convert(Duration duration) { ... }
这是为了支持 java.time.Duration 的转换。
Duration内部用秒 + 纳秒调整来表示时间量。- 处理了
secs < 0 && nano > 0的特殊情况:
比如Duration.ofSeconds(-1, 500_000_000)表示 -0.5 秒。duration.getSeconds()返回 -1,nano返回 500_000_000。
为了与整数除法语义对齐,代码调整secs++,nano -= SECOND_SCALE,变为secs = 0,nano = -500_000_000。 - 然后根据不同目标单位进行优化:
- 如果是
NANOSECONDS,直接计算secs * secRatio + nano,其中secRatio = 1_000_000_000。 - 如果目标单位 < 秒,先将
nano除以该单位的scale。 - 如果目标单位是秒,直接返回
secs。 - 更大单位:
secs / secRatio。
- 如果是
- 最后做溢出检查:如果
secs在安全范围内直接返回,否则根据符号返回Long.MAX_VALUE或Long.MIN_VALUE。
这个方法同样不抛异常,采用饱和策略,与 Duration.toNanos() 不同。
八、toNanos、toMicros、toMillis、toSeconds
这些方法是转换为具体单位的高频入口,它们的共同模式是:
- 检查
scale与目标常量的关系,决定直接返回、除法还是乘法。 - 乘法时使用构造时缓存好的
maxXxx边界来避免溢出,溢出则饱和到MAX_VALUE或MIN_VALUE。
以 toMillis 为例:
public long toMillis(long duration) {
long s, m;
if ((s = scale) <= MILLI_SCALE)
return (s == MILLI_SCALE) ? duration : duration / milliRatio;
else if (duration > (m = maxMillis))
return Long.MAX_VALUE;
else if (duration < -m)
return Long.MIN_VALUE;
else
return duration * milliRatio;
}
- 若当前单位 ≤ 毫秒(即更精细),则要么相等直接返回,要么用除法(
milliRatio = MILLI_SCALE / scale,向上取整的分母)。 - 若当前单位 > 毫秒,则需要乘法,并用
maxMillis做溢出检查。
toMinutes、toHours、toDays 则直接调用 cvt,没有单独缓存。
九、excessNanos:计算不满 1 毫秒的纳秒部分
private int excessNanos(long d, long m) { ... }
d是总的 timeout(当前单位数值),m是已经转成毫秒的部分(通过toMillis得到)。- 此方法计算需要额外传递给
wait(millis, nanos)的纳秒部分。 - 如果当前单位是纳秒:
d - (m * MILLI_SCALE)算出剩余纳秒。 - 如果当前单位是微秒:
d * 1000 - m * MILLI_SCALE。 - 其他单位(毫秒及以上),纳秒部分为 0。
十、定时等待方法:timedWait、timedJoin、sleep
这三个方法将 TimeUnit 的数值转换成 Object.wait(long ms, int ns)、Thread.join(long ms, int ns)、Thread.sleep(long ms, int ns) 所需的参数。
以 sleep 为例:
public void sleep(long timeout) throws InterruptedException {
if (timeout > 0) {
long ms = toMillis(timeout);
int ns = excessNanos(timeout, ms);
Thread.sleep(ms, ns);
}
}
- 如果
timeout <= 0,什么都不做(与Thread.sleep(0)语义不同,但这是并发工具类的常见处理,避免负值异常)。 - 转换为毫秒和剩余纳秒,调用底层方法。这样用户可以写
TimeUnit.SECONDS.sleep(1)来睡眠 1 秒,代码更具可读性。
十一、与 java.time.temporal.ChronoUnit 互相转换(Java 9+)
public ChronoUnit toChronoUnit() { ... }
public static TimeUnit of(ChronoUnit chronoUnit) { ... }
这两个方法实现了 TimeUnit 和 ChronoUnit 的双向映射,让新旧 API 能互操作。例如:
TimeUnit.SECONDS.toChronoUnit()→ChronoUnit.SECONDSTimeUnit.of(ChronoUnit.HOURS)→TimeUnit.HOURS
对于 ChronoUnit 中没有直接对应的单位(如 WEEKS、MONTHS),of 会抛出 IllegalArgumentException。
总结
整个 TimeUnit 的设计体现了高性能、类型安全、防御式编程的理念:
- 枚举保证类型安全:不会传递错误的整数当作时间单位。
- 预计算与缓存:构造时就把常用转换的比率和溢出边界算好,运行时几乎无额外开销。
- 饱和策略:所有转换不会抛
ArithmeticException,而是返回Long.MAX/MIN_VALUE,避免程序因溢出中断(但也需要调用者自己注意)。 - 便捷的定时方法:让
sleep、wait、join代码更可读,且内部正确分解毫秒和纳秒参数。 - 与
java.time兼容:通过Duration转换和ChronoUnit互转,桥接新旧 API。
这就是 TimeUnit 的全部核心含义与实现细节。
结合给出的源码和注释,我逐一回答你的四个问题。
1. TimeUnit 和 Lock 的关系是什么?
TimeUnit 与 Lock 之间是 “参数解释器”与“使用者” 的关系。
Lock接口中定义了带超时的方法,例如tryLock(long time, TimeUnit unit)。- 这里的
time只是一个数值,本身没有单位含义;TimeUnit的作用就是告诉Lock:“这个time数值应该按照什么时间粒度来解读”。
从注释中的例子可以清楚看到这一点:
// 50 毫秒后超时
if (lock.tryLock(50L, TimeUnit.MILLISECONDS)) ...
// 50 秒后超时
if (lock.tryLock(50L, TimeUnit.SECONDS)) ...
同一个数值 50L,因为配合的 TimeUnit 不同,一个解释为 50 毫秒,一个解释为 50 秒。Lock 的实现内部通常会使用 TimeUnit 提供的方法(比如 toMillis、toNanos)将任意单位的时间统一转换为自己需要的精度,进而进行底层的定时等待或自旋。
另外,TimeUnit 还提供 timedWait、timedJoin、sleep 等便利方法,它们都可以直接用于基于 synchronized 的等待或线程控制,这和 Lock 中的超时机制属于同一类并发原语的辅助。比如你可以用 unit.timedWait(lock, timeout) 来实现一个阻塞队列的 poll 方法,这在注释中也有示例:
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
synchronized (lock) {
while (isEmpty()) {
unit.timedWait(lock, timeout);
...
}
}
}
总结: TimeUnit 不拥有锁,也不实现锁机制,它是作为参数传给锁的方法,用于定义超时时间的单位,从而让锁的接口在时间表达上既灵活又类型安全。
2. TimeUnit 怎么处理并发问题?
需要从两个层面理解:
(1)TimeUnit 自身的线程安全性
TimeUnit 是一个枚举,所有实例(如 NANOSECONDS、SECONDS 等)都是在类加载时创建的常量。
它的所有字段(scale、maxNanos、secRatio 等)都是 private final 的,在构造完成后就不可变。
所有方法(convert、toNanos、sleep 等)都是无状态的纯计算,只依赖方法参数和自身不可变字段,不修改任何共享状态。
因此,TimeUnit 是天然线程安全的,多个线程可以任意并发调用同一个 TimeUnit 实例的方法,而不需要任何外部同步。
(2)TimeUnit 在并发场景中的作用
TimeUnit 并不自己去“处理并发问题”(如加锁、协调线程),而是为并发工具提供时间解释服务。它和并发工具的关系是:
- 为
Lock、Condition、Future、Semaphore等提供超时参数的单位,使代码可读性更强且不易出错。 - 内置
timedWait、timedJoin、sleep方法,这些方法内部将当前TimeUnit的时间值转换为毫秒和纳秒,再调用Object.wait(ms, ns)、Thread.join(ms, ns)或Thread.sleep(ms, ns)来实现等待。它们本质上是在调用底层操作系统的定时等待机制,并发控制仍然由 JVM 和操作系统负责,TimeUnit只是做了数值转换。
例如 timedWait 的实现:
public void timedWait(Object obj, long timeout) throws InterruptedException {
if (timeout > 0) {
long ms = toMillis(timeout);
int ns = excessNanos(timeout, ms);
obj.wait(ms, ns);
}
}
它只是算出毫秒和剩余纳秒,然后调用 obj.wait()。这种设计让并发代码不用再手动拆分时间值,减少了出错几率。
结论: TimeUnit 自身是不可变枚举,线程安全;它通过提供单位转换和便利方法,间接支持了并发编程中对超时、等待、睡眠等操作的精确控制,但实际的并发互斥由 Lock、synchronized 等机制处理。
3. TimeUnit 内部最大的单位和最小的单位是什么?
根据枚举常量定义,结合比例常量:
NANOSECONDS(TimeUnit.NANO_SCALE), // NANO_SCALE = 1
MICROSECONDS(TimeUnit.MICRO_SCALE), // MICRO_SCALE = 1000
MILLISECONDS(TimeUnit.MILLI_SCALE), // MILLI_SCALE = 1000 * 1000
SECONDS(TimeUnit.SECOND_SCALE), // SECOND_SCALE = 1000 * 1000 * 1000
MINUTES(TimeUnit.MINUTE_SCALE), // 60 * SECOND_SCALE
HOURS(TimeUnit.HOUR_SCALE), // 60 * MINUTE_SCALE
DAYS(TimeUnit.DAY_SCALE) // 24 * HOUR_SCALE
- 最小单位:
NANOSECONDS(纳秒),其基准比例NANO_SCALE = 1。 - 最大单位:
DAYS(天),其比例DAY_SCALE = 24 * HOUR_SCALE,即 86,400 秒的纳秒数。
注释中也明确说明:
A nanosecond is defined as one thousandth of a microsecond, a microsecond as one thousandth of a millisecond, a millisecond as one thousandth of a second, a minute as sixty seconds, an hour as sixty minutes, and a day as twenty four hours.
所以时间粒度从纳秒一直到天,覆盖了绝大多数业务和系统编程的需要。
4. 如何处理单位之间的转化?
单位转化是 TimeUnit 的核心能力,主要通过以下方式实现,并始终伴随溢出保护。
(1)核心通用方法 cvt
private static long cvt(long d, long dst, long src) {
long r, m;
if (src == dst)
return d;
else if (src < dst)
return d / (dst / src);
else if (d > (m = Long.MAX_VALUE / (r = src / dst)))
return Long.MAX_VALUE;
else if (d < -m)
return Long.MIN_VALUE;
else
return d * r;
}
逻辑如下:
- 同单位:直接返回原值。
- 源单位小于目标单位(例如纳秒 → 秒,越精细 → 越粗):
用 整数除法 截断,d / (dst / src)。
注释也注明:转换从细粒度到粗粒度会 truncate(截断),丢失精度。例如999毫秒转秒 →0。 - 源单位大于目标单位(例如秒 → 纳秒,越粗 → 越细):
需要乘法。乘法前检查溢出:r = src / dst(一个 src 单位等于多少个 dst 单位),m = Long.MAX_VALUE / r(安全上限)。
若d > m则饱和为Long.MAX_VALUE;若d < -m则饱和为Long.MIN_VALUE;否则安全相乘。
这样,任何转换都不会抛出 ArithmeticException,而是采用饱和策略,和 Duration.toNanos() 的行为不同(Duration.toNanos() 会抛异常)。
(2)高频单位的快速通道:toNanos、toMicros、toMillis、toSeconds
构造时为秒及以下单位预先计算了 转换比率(microRatio, milliRatio, secRatio)和 安全边界(maxNanos, maxMicros, maxMillis, maxSecs)。这些 toXxx() 方法直接利用缓存值,避免重复计算,性能极高。
以 toMillis 为例:
public long toMillis(long duration) {
long s, m;
if ((s = scale) <= MILLI_SCALE)
return (s == MILLI_SCALE) ? duration : duration / milliRatio;
else if (duration > (m = maxMillis))
return Long.MAX_VALUE;
else if (duration < -m)
return Long.MIN_VALUE;
else
return duration * milliRatio;
}
- 若当前单位 ≤ 毫秒,则返回原值(相等时)或除以
milliRatio(更细单位向毫秒转换的除数)。 - 若当前单位 > 毫秒,则乘以
milliRatio,并用预存的maxMillis做溢出检测。
(3)convert 方法(分派)
public long convert(long sourceDuration, TimeUnit sourceUnit) {
switch (this) {
case NANOSECONDS: return sourceUnit.toNanos(sourceDuration);
case MICROSECONDS: return sourceUnit.toMicros(sourceDuration);
case MILLISECONDS: return sourceUnit.toMillis(sourceDuration);
case SECONDS: return sourceUnit.toSeconds(sourceDuration);
default: return cvt(sourceDuration, scale, sourceUnit.scale);
}
}
convert 将源单位的值转成当前单位(this)。它把最常用的四种目标单位(纳秒、微秒、毫秒、秒)直接分派到对应的快速方法;对于分、时、天则退回到 cvt。
(4)与 Duration 的转换
Java 11 后加入 convert(Duration duration),它提取 Duration 的秒和纳秒部分,经过调整(处理负数的正纳秒表示)后,再根据不同目标单位用不同路径计算,同样做溢出饱和保护。
(5)定时等待中的特殊转化:excessNanos
在 sleep、wait、join 中,需要把时间拆成“毫秒部分”和“不足 1 毫秒的纳秒部分”:
private int excessNanos(long d, long m) {
long s;
if ((s = scale) == NANO_SCALE)
return (int)(d - (m * MILLI_SCALE));
else if (s == MICRO_SCALE)
return (int)((d * 1000L) - (m * MILLI_SCALE));
else
return 0;
}
这里 d 是原数值,m 是已经转化出的毫秒数,该方法算出剩余纳秒,精确传递给底层 wait(ms, ns) 等方法。
整体来说,TimeUnit 的单位转换设计兼顾了:
- 正确性:整数除法截断、乘法溢出饱和,避免静默错误。
- 性能:常用单位缓存比率和边界,高频调用零开销。
- 可读性:
convert、toXxx等方法让调用代码清晰表达意图。 - 安全性:所有转换都不抛异常,采用最安全的长整型饱和策略。
作用
这个 TimeSource 接口很好地展示了 TimeUnit 在解耦时间来源与时间单位上的设计价值。下面我们先从 Minecraft 的设计意图切入,再看企业开发里的经典场景,让你对 TimeUnit 的作用有立体的认识。
一、从 Minecraft 的 TimeSource 理解 TimeUnit 的作用
源码回顾
@FunctionalInterface
public interface TimeSource {
long get(TimeUnit unit);
interface NanoTimeSource extends TimeSource, LongSupplier {
default long get(TimeUnit unit) {
return unit.convert(this.getAsLong(), TimeUnit.NANOSECONDS);
}
}
}
设计意图
TimeSource是一个时间源抽象,不规定具体来源(系统时间、游戏刻、自定义时钟等)。它只有一个方法long get(TimeUnit unit),意为“以你指定的单位返回当前时间”。NanoTimeSource是一种基于纳秒的实现。它同时继承了LongSupplier(提供long getAsLong()),在get(TimeUnit)中调用unit.convert(this.getAsLong(), TimeUnit.NANOSECONDS),将底层纳秒值转换成任意单位。
为什么需要 TimeUnit 作为参数?
如果 TimeSource 只是固定返回纳秒或毫秒,那么:
- 调用方必须自己转换单位,代码里会散落大量除法/乘法,容易出错。
- 不同时间源的精度不同:游戏刻(tick)可能用“刻数”表示时间,Minecraft 的 20 tick = 1 秒。若有一个基于 tick 的
TimeSource,它能通过TimeUnit直接把内部 tick 数转成秒、毫秒等,调用方无需关心内部单位。
TimeUnit 在这里提供了一个统一的转换协议:底层实现只需用自己最自然的单位记录时间,然后在 get(unit) 中使用 unit.convert(...) 或 sourceUnit.toXxx(...) 输出给调用方。调用方可以自由选择自己关心的精度(性能分析用纳秒,业务超时用秒),而不破坏底层封装。
Minecraft 中的实际用法举例
- 性能分析:
MinecraftServer的 tick 循环里可能有TimeSource.NanoTimeSource,用于测量单 tick 耗时。
例如:long elapsed = timeSource.get(TimeUnit.MILLISECONDS)直接得到毫秒数。 - 异步任务调度:计划任务可能接受
TimeUnit参数,让配置更可读:schedule(task, 5, TimeUnit.SECONDS)。 - 多平台移植:不同平台系统时间精度不同,通过
TimeSource抽象,可替换实现而不影响上层逻辑。
二、企业实际开发中 TimeUnit 的核心作用与示例
TimeUnit 在 Java 企业开发中几乎无处不在,主要有以下几个作用:
- 让时间间隔的语义清晰,消除魔法数字
- 统一单位转换,避免手工计算和溢出风险
- 配合并发工具类,简化超时、延迟、等待的代码
- 实现性能计时、限流、缓存过期等逻辑
- 与监控、日志、接口设计结合,提升可读性和可维护性
1. 消除魔法数字,提高代码可读性
没有 TimeUnit 时,你可能会写:
// 超时 3 秒——这是毫秒还是秒?容易混淆
future.get(3000, TimeUnit.MILLISECONDS); // 不好
更好的写法直接使用 TimeUnit 常量作为参数:
// 清晰表达“3 秒超时”
future.get(3, TimeUnit.SECONDS);
这在 API 设计中尤其重要,例如自定义服务的超时配置:
public class ServiceConfig {
private long timeout;
private TimeUnit timeoutUnit = TimeUnit.MILLISECONDS;
public void setTimeout(long timeout, TimeUnit unit) {
this.timeout = timeout;
this.timeoutUnit = unit;
}
public long getTimeoutMillis() {
return timeoutUnit.toMillis(timeout);
}
}
调用方可以自由设置:config.setTimeout(5, TimeUnit.SECONDS),而内部统一转为毫秒,既灵活又安全。
2. 配合并发工具,简化超时/延迟控制
JUC 下的 Lock、Condition、Future、CompletableFuture、Semaphore 等很多方法的超时参数都直接使用 TimeUnit:
// 可重入锁尝试获取锁,最多等 500 毫秒
Lock lock = new ReentrantLock();
if (lock.tryLock(500, TimeUnit.MILLISECONDS)) {
try {
// 处理逻辑
} finally {
lock.unlock();
}
} else {
// 获取失败降级
}
线程池的 awaitTermination 同样依赖 TimeUnit:
ExecutorService executor = Executors.newFixedThreadPool(4);
// ... 提交任务
executor.shutdown();
if (!executor.awaitTermination(10, TimeUnit.SECONDS)) {
executor.shutdownNow(); // 强制关闭
}
sleep、wait、join 也可以通过 TimeUnit 得到更优雅的表达:
TimeUnit.SECONDS.sleep(1); // 代替 Thread.sleep(1000)
TimeUnit.MINUTES.timedJoin(workerThread, 2); // 最多等 2 分钟
3. 性能监控与代码计时
用 System.nanoTime() 结合 TimeUnit 可以方便地实现高精度计时,并直接输出你想要的时间单位:
long start = System.nanoTime();
// 执行业务逻辑
doSomething();
long elapsedNanos = System.nanoTime() - start;
// 直接输出为毫秒
long elapsedMillis = TimeUnit.NANOSECONDS.toMillis(elapsedNanos);
logger.info("执行耗时:{} ms", elapsedMillis);
// 或者用 Duration(内部也依赖 TimeUnit/ChronoUnit)
Duration duration = Duration.ofNanos(elapsedNanos);
System.out.println(duration.toMillis() + " ms");
更优雅的封装(类似 TimeSource):
public class Stopwatch {
private long startNanos;
public Stopwatch start() {
this.startNanos = System.nanoTime();
return this;
}
public long elapsed(TimeUnit unit) {
return unit.convert(System.nanoTime() - startNanos, TimeUnit.NANOSECONDS);
}
}
// 使用
Stopwatch sw = new Stopwatch().start();
// ... 操作
System.out.println("耗时:" + sw.elapsed(TimeUnit.MICROSECONDS) + " µs");
4. 限流与速率控制
Guava 的 RateLimiter 内部就重度依赖 TimeUnit,但我们可以用 TimeUnit 自己实现简单的令牌桶或固定窗口限流:
public class SimpleRateLimiter {
private final long intervalNanos;
private long nextAllowedNanos = System.nanoTime();
public SimpleRateLimiter(long permitsPerSecond) {
this.intervalNanos = TimeUnit.SECONDS.toNanos(1) / permitsPerSecond;
}
public synchronized boolean tryAcquire() {
long now = System.nanoTime();
if (now >= nextAllowedNanos) {
nextAllowedNanos = now + intervalNanos;
return true;
}
return false;
}
// 带超时的等待获取
public boolean tryAcquire(long timeout, TimeUnit unit) throws InterruptedException {
long deadline = System.nanoTime() + unit.toNanos(timeout);
while (System.nanoTime() < deadline) {
if (tryAcquire()) return true;
TimeUnit.MICROSECONDS.sleep(100); // 短暂休眠避免忙等
}
return false;
}
}
调用:
SimpleRateLimiter limiter = new SimpleRateLimiter(10); // 每秒 10 个
if (limiter.tryAcquire(500, TimeUnit.MILLISECONDS)) {
// 允许请求
}
5. 缓存过期与定时任务
缓存库(如 Caffeine、Guava Cache)都使用 TimeUnit 指定过期时间。我们可以写一个简单的缓存过期实现:
public class TimedCache<K, V> {
private final ConcurrentHashMap<K, V> cache = new ConcurrentHashMap<>();
private final ConcurrentHashMap<K, Long> expiryMap = new ConcurrentHashMap<>();
private final long ttlNanos;
public TimedCache(long ttl, TimeUnit unit) {
this.ttlNanos = unit.toNanos(ttl);
}
public void put(K key, V value) {
cache.put(key, value);
expiryMap.put(key, System.nanoTime() + ttlNanos);
}
public V get(K key) {
Long expiry = expiryMap.get(key);
if (expiry == null || System.nanoTime() > expiry) {
cache.remove(key);
expiryMap.remove(key);
return null;
}
return cache.get(key);
}
}
// 使用:条目 10 秒后过期
TimedCache<String, User> sessionCache = new TimedCache<>(10, TimeUnit.SECONDS);
在调度任务时,ScheduledExecutorService 也直接使用 TimeUnit:
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
// 延迟 5 秒执行,之后每隔 2 秒执行一次
scheduler.scheduleAtFixedRate(
() -> System.out.println("清理临时文件..."),
5, 2, TimeUnit.SECONDS
);
6. 与 java.time.Duration 互转,桥接新旧 API
企业开发中经常需要将配置中的 Duration 转换为并发库所需的 (long, TimeUnit) 参数:
Duration timeout = Duration.ofSeconds(30);
// 转换为 TimeUnit 调用
future.get(timeout.toMillis(), TimeUnit.MILLISECONDS);
// 或直接用 TimeUnit 的 convert 方法
long seconds = TimeUnit.MILLISECONDS.convert(timeout.toMillis(), TimeUnit.MILLISECONDS);
TimeUnit 本身也提供了 toChronoUnit() 和 TimeUnit.of(ChronoUnit) 实现与 ChronoUnit 互转,使得 Spring、Guava 等框架能无缝衔接。
三、总结
TimeUnit 作为一个枚举工具类,在企业开发中的核心价值是:
- 类型安全:避免整数参数含义不明。
- 转换安全:内部处理溢出并采用饱和策略,避免
ArithmeticException。 - 代码自文档化:看到
5, TimeUnit.SECONDS就知道是 5 秒。 - 性能优化:通过预缓存缩放因子和边界值,转换开销极低。
结合 Minecraft 的 TimeSource,我们看到了 TimeUnit 如何帮助抽象时间源:底层可以是纳秒、毫秒甚至游戏刻,上层只需通过 TimeUnit 获取任意单位的值,既保持底层精度,又让上层逻辑单位无关,这是多精度时间系统里非常典型的设计范式。
如果你正在设计自己的框架或工具类,凡是需要传递“时间长度”的地方,建议都用 (long duration, TimeUnit unit) 这样的方法签名,这会大幅度提升你 API 的易用性和稳健性。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)