时间轮Hash算法

251 阅读3分钟

时间轮(Hashed Wheel)原理

HashedWheelTimer 的核心思想是利用一个“时间轮”数据结构,这个时间轮是一个环形缓冲区,分为若干个槽,每个槽代表一个固定的时间片。时间轮的每个槽中保存一组定时任务,任务会在其到期时被触发。

基本概念
  • Tick:时间轮每转动一次称为一个“tick”,每个 tick 对应一段固定的时间(例如 1ms、10ms、100ms 等)。
  • Bucket:时间轮上的每个槽(slot)叫做一个“桶”,每个桶存储一组定时任务。
  • 时间轮的轮次:时间轮的结构实际上是一个固定大小的数组(wheel[]),这些槽呈环形排列。

时间轮工作流程

  1. 任务添加:每个定时任务都关联一个到期时间和一个时间间隔(tick)。任务被添加到对应的时间轮槽中。
  2. 轮转:时间轮不断旋转(即“ticks”),每次轮转一个 tick,所有槽都会按顺序检查一次。任务到期时会被执行。
  3. 任务移除:当任务的到期时间到来时,会从时间轮的对应槽中移除并执行。

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;

四个核心类

9c11938c-6a98-4306-bb98-7059d49c1a08.jpeg

代码解析

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, 否则会数值溢出。


方法解析

image.png

image.png

image.png

image.png

image.png