单层时间轮的弊端
对于单层时间轮来说,如果要拉长延迟的时间,要么增加时间轮中槽的数量,要么增大前进指针的时间间隔。但是这两种方式都有弊端
- 增加槽的数量:占用内存较高
- 增大前进指针的时间间隔:延迟时间不精确 为了解决上面的问题,我们采用钟表中的秒针、分针、时针一样,通过多层时间轮来挂载延迟任务,这样就可以用少量的内存实现超长的延迟时间。
多层时间轮的结构
时间槽:每个时间轮包含若干时间槽,每个槽代表一个时间片段。 时间轮:多层时间轮通常包含多个时间轮,每一层的精度和范围不同。例如,第一层(细粒度)可以管理毫秒级任务,第二层(中等粒度)可以管理秒级任务,第三层(粗粒度)可以管理分钟级任务。 指针:每个时间轮都有一个指针,用于指向当前时间槽,随着时间的推移指针向前移动。 任务链表:每个时间槽中包含一个任务链表,用于存储定时任务。
代码实战
任务
public class RecursiveTimeWheel {
private static final int MS_PER_TICK = 10;
private static final int[] WHEEL_SIZES = {100, 60, 60}; // 毫秒、秒、分钟
private static final String[] WHEEL_NAMES = {"毫秒", "秒", "分钟"};
private final List<TimeWheel> wheels = new ArrayList<>();
private final ExecutorService executor = Executors.newFixedThreadPool(4);
private final AtomicInteger taskCounter = new AtomicInteger(0);
public RecursiveTimeWheel() {
// 递归创建多级时间轮
TimeWheel prevWheel = null;
for (int i = 0; i < WHEEL_SIZES.length; i++) {
final int level = i;
TimeWheel wheel = new TimeWheel(
WHEEL_SIZES[i],
prevWheel,
tasks -> advanceCallback(level, tasks)
);
wheels.add(wheel);
prevWheel = wheel;
}
new Thread(this::drive, "TimeWheel-Driver").start();
}
public void schedule(long delayMs, Runnable task) {
long delayTicks = delayMs / MS_PER_TICK;
if (delayTicks <= 0) {
executor.execute(task);
return;
}
// 递归添加任务
addTaskRecursively(delayTicks, task, wheels.size() - 1);
System.out.printf("Task %d scheduled with delay %dms%n",
taskCounter.incrementAndGet(), delayMs);
}
// 递归添加任务
private void addTaskRecursively(long remainingTicks, Runnable task, int wheelLevel) {
if (wheelLevel < 0) {
executor.execute(task);
return;
}
TimeWheel wheel = wheels.get(wheelLevel);
int wheelSize = wheel.size;
long wheelTicks = calculateWheelTicks(wheelLevel);
if (remainingTicks < wheelTicks || wheelLevel == 0) {
// 当前是最低级时间轮或者剩余时间可以在当前轮处理
int ticks = (int) remainingTicks;
wheel.addTask(ticks, () -> {
if (wheelLevel > 0) {
// 还有下级轮,继续递归
addTaskRecursively(remainingTicks - ticks, task, wheelLevel - 1);
} else {
task.run();
}
});
} else {
// 需要更高级时间轮处理
int currentWheelTicks = (int) (remainingTicks / wheelTicks);
long nextRemainingTicks = remainingTicks % wheelTicks;
// 这里对多级时间轮的理解很关键:如果有余数,则需要对当前任务再包装个任务,当执行到当前层级的任务时,其实是执行了addTaskRecursively方法,而该方法又把元任务根据nextRemainingTicks放置在下一层级的某个预期的槽里,当时间真正到的时候才执行到元任务。以这样的方式实现了任务下放。
wheel.addTask(currentWheelTicks, () ->
addTaskRecursively(nextRemainingTicks, task, wheelLevel)
);
}
}
// 计算当前级别时间轮的总ticks
private long calculateWheelTicks(int level) {
long ticks = 1;
for (int i = 0; i < level; i++) {
ticks *= WHEEL_SIZES[i];
}
return ticks;
}
private void advanceCallback(int level, List<Runnable> tasks) {
if (!tasks.isEmpty()) {
String wheelName = WHEEL_NAMES[level];
if (level == 0) {
System.out.println("执行 " + tasks.size() + " 个" + wheelName + "级任务");
tasks.forEach(executor::execute);
} else {
System.out.println("将 " + tasks.size() + " 个任务从" + wheelName + "轮降级");
tasks.forEach(Runnable::run);
}
}
}
private void drive() {
try {
while (!Thread.currentThread().isInterrupted()) {
Thread.sleep(MS_PER_TICK);
wheels.get(0).advance(); // 驱动最底层时间轮
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
private static class TimeWheel {
private final int size;
private final TimeWheel overflowWheel;
private final java.util.function.Consumer<List<Runnable>> onTick;
private final List<Runnable>[] buckets;
private int currentTick;
@SuppressWarnings("unchecked")
TimeWheel(int size, TimeWheel overflowWheel, java.util.function.Consumer<List<Runnable>> onTick) {
this.size = size;
this.overflowWheel = overflowWheel;
this.onTick = onTick;
this.buckets = new List[size];
for (int i = 0; i < size; i++) {
buckets[i] = new ArrayList<>();
}
this.currentTick = 0;
}
void addTask(int ticks, Runnable task) {
int bucket = (currentTick + ticks) % size;
buckets[bucket].add(task);
}
void advance() {
currentTick = (currentTick + 1) % size;
List<Runnable> tasks = new ArrayList<>(buckets[currentTick]);
buckets[currentTick].clear();
onTick.accept(tasks);
if (currentTick == 0 && overflowWheel != null) {
overflowWheel.advance();
}
}
}
public static void main(String[] args) throws InterruptedException {
RecursiveTimeWheel timer = new RecursiveTimeWheel();
// 测试不同级别的定时任务
long startTime = System.currentTimeMillis();
timer.schedule(500, () -> System.out.println("500ms任务执行 at: " + (System.currentTimeMillis() - startTime)));
timer.schedule(1500, () -> System.out.println("1.5s任务执行 at: " + (System.currentTimeMillis() - startTime)));
timer.schedule(30_500, () -> System.out.println("30.5s任务执行 at: " + (System.currentTimeMillis() - startTime)));
timer.schedule(90_500, () -> System.out.println("1分30.5秒任务执行 at: " + (System.currentTimeMillis() - startTime)));
Thread.sleep(120_000);
System.out.println("测试完成");
}
}
以上是通过递归的形式优雅地实现了多级时间轮的关键逻辑,有助于读者更深刻地理解多级时间轮的实现方式。