时间轮(Hashed Wheel)原理
HashedWheelTimer 的核心思想是利用一个“时间轮”数据结构,这个时间轮是一个环形缓冲区,分为若干个槽,每个槽代表一个固定的时间片。时间轮的每个槽中保存一组定时任务,任务会在其到期时被触发。
基本概念
- Tick:时间轮每转动一次称为一个“tick”,每个 tick 对应一段固定的时间(例如 1ms、10ms、100ms 等)。
- Bucket:时间轮上的每个槽(slot)叫做一个“桶”,每个桶存储一组定时任务。
- 时间轮的轮次:时间轮的结构实际上是一个固定大小的数组(
wheel[]),这些槽呈环形排列。
时间轮工作流程
- 任务添加:每个定时任务都关联一个到期时间和一个时间间隔(tick)。任务被添加到对应的时间轮槽中。
- 轮转:时间轮不断旋转(即“ticks”),每次轮转一个 tick,所有槽都会按顺序检查一次。任务到期时会被执行。
- 任务移除:当任务的到期时间到来时,会从时间轮的对应槽中移除并执行。
HashedWheelTimer 主要结构
HashedWheelTimer 通常包含以下关键组件:
1. 时间轮数组(wheel[])
时间轮是一个数组,数组的每个元素对应时间轮中的一个槽(bucket),每个槽中存储定时任务。时间轮数组大小是 wheel.length,设置为 2的最小幂次方,以提高效率。
private final HashedWheelBucket[] wheel;
2. 当前时间刻度(tick)
tick 表示当前时间轮转的刻度数。在每次轮转时,tick 会自增。每个任务会根据其到期时间被分配到某个槽(bucket),并且随着时间轮的旋转,任务会逐渐到期。
private long tick;
3. 任务队列(timeouts)
timeouts 是一个队列,保存待处理的超时任务。定时任务会从这个队列中取出,并分配到时间轮的对应槽中。
private final Queue<HashedWheelTimeout> timeouts;
4. 轮转间隔(tickDuration)
tickDuration 是每个 tick 对应的时间长度。时间轮每转动一次,会经过一个固定的时间间隔。
private final long tickDuration;
5. 工作线程(workerThread)
时间轮的执行通常是通过一个后台线程来完成的,该线程定期轮转时间轮,检查各个槽中的任务是否到期。
private final Thread workerThread;
四个核心类
代码解析
Timer: 顶级接口,定义了三个方法
// 初始化任务
1. Timeout newTimeout(TimerTask task, long delay, TimeUnit unit);
// 释放所有资源, 同时取消被调度但是还未执行的任务集合(返回值)
2. Set<Timeout> stop();
// 判断Timer是否停止
3. boolean isStop();
HashedWheelTimer: Timer实现类, 时间轮算法
1. 初始化参数:
ThreadFactory threadFactory: 线程池工厂, 默认=Executors.defaultThreadFactory()
long tickDuration: tick的刻度(时间粒度): 默认为100,必须大于0,时间轮每跳动一次的时间间隔。
TimeUnit unit: tick刻度的单位, 默认ms
int ticksPerWheel: 多少个刻度(槽数), 默认512, 必须大于0, 不能超过2^30次方
long maxPendingTimeouts: 最大等待超时的任务数量, -1或者0则不限制
备注:tickDuration * ticksPerWheel = 两者的乘积表示时间轮完整旋转一圈所需的时间,通常必须是合理的时间量。
时间粒度需要满足 < Long.MAX_VALUE / ticksPerWheel, 否则会数值溢出。
方法解析