时间轮-【gitee】
使用了一些jdk9+的特性语法,但是可以降级到jdk8甚至更低,功能没变化只是缺少了一些jdk高版本的优化。
可以自定义时间推进粒度()和时间轮格子大小。 支持定时执行,循环执行,以及随机下次执行时间。
主要分为两个组件,DripTimeWheel.java和DripTicker.java
一个是时间轮本体,一个是时间推进器。
关于时间推进器
- 独立高优先级线程,以固定周期(如 10ms)精准推进“tick”。
- 以自然秒对齐为起点,启动时自动校准,初始误差通常小于 1 毫秒。
// 以一个自然秒的开始作为基准时间开始推进,误差通常在1ms内
// 获取当前时间距离上个整秒的差值纳秒
// 估算整秒起点对应的纳秒时间(这两个时间无法原子获取,但误差 <1ms)
long currentTimeMillis = System.currentTimeMillis();
long nowNanos = System.nanoTime();
long nanos = TimeUnit.MILLISECONDS.toNanos(currentTimeMillis % 1000);
final long startNanos = nowNanos - nanos;
- 采用 混合等待策略:长时间等待使用
LockSupport.parkNanos释放 CPU,短时间(<2ms)使用Thread.onSpinWait()自旋,兼顾精度与功耗。
// ------------------------------ 混合等待策略 ------------------------------
private long awaitUntilTargetTime(long targetNanos) {
while (true) {
long currentNanos = System.nanoTime();
long remainingNanos = targetNanos - currentNanos;
// 1. 已到达/超出目标时间,返回剩余时间(负数/零)
if (remainingNanos <= 0) {
return remainingNanos;
}
// 2. 剩余时间>10ms:阻塞99%的时间(释放CPU,低消耗),阻塞一次后一定能进入自旋,通常剩余1ms以下
if (remainingNanos > SPIN_THRESHOLD_NANOS) {
LockSupport.parkNanos(remainingNanos - 100_000); // 留0.2ms自旋
} else {
// 3. 剩余时间≤10ms:智能自旋(无唤醒延迟,高精度)
// 这个是jdk9的优化,不优化也行,就是资源消耗多一点点
Thread.onSpinWait();
}
// 4. 响应关闭/中断信号
if (!isRunning || isShutdown || Thread.currentThread().isInterrupted()) {
return 0;
}
}
}
- 支持自动追赶:若因 GC 或系统卡顿跳过多个 tick,会根据当前系统时间直接跳转到正确的 tick,避免任务堆积或漏调。
- 有一个专门的单线程负责推进时间不会被其他任务占用
关于时间轮
- 数据结构分为主轮和溢出区
- 主轮存放马上要执行的任务,负责高效存取执行任务。
- 溢出区存放等待时间长,暂时不需要执行的任务,会在合适的时间取出放入主轮
- 预加载机制:每次推进时,将未来一轮内即将到期的任务从溢出区加载到主轮,确保主轮始终“热”。
- 专门的单线程池负责移动存取任务,保证整个时间轮结构不会有并发问题。
- 用户自己传入真正执行任务的线程池,根据同一时刻的任务峰值调整任务线程池大小,否则会因此卡住
关于task
会有专门的Task结构保存任务,可以设置延迟执行时间、循环执行、取消等操作。 取消操作可以随时执行,但是取消的任务无法再次发起
示例
/**
* 使用100ms的时间推进,这里用256格的时间轮,25.6秒一轮
**/
private static final DripTimeWheel dripTimeWheel = new DripTimeWheel(
100,
256,
(_, task) -> task.getTask().run(),
5000,
executorService
);
/**
* 固定延迟执行定时任务,并以固定时间循环
**/
public DripTask scheduleAtFixedRate(Runnable command, Duration delay, Duration period) {
DripFixedCycleTask dripTask = new DripFixedCycleTask(dripTimeWheel.getTicker(), command, delay, period);
dripTimeWheel.schedule(dripTask);
return dripTask;
}
/**
* 固定延迟执行定时任务,以随机范围内的时间循环
**/
public DripTask scheduleAtRandomRate(Runnable command, Duration delay, Duration minPeriod, Duration maxPeriod) {
DripRandomCycleTask dripTask = new DripRandomCycleTask(dripTimeWheel.getTicker(), command, delay, minPeriod, maxPeriod);
dripTimeWheel.schedule(dripTask);
return dripTask;
}
/**
* 固定延迟执行
**/
public DripTask scheduleAt(Runnable command, Duration delay) {
DripDelayTask dripTask = new DripDelayTask(dripTimeWheel.getTicker(), command, delay);
dripTimeWheel.schedule(dripTask);
return dripTask;
}