HashedWheelTimer实现定时调度任务
HashedWheelTimer 主要用来高效处理大量定时任务, 且任务对时间精度要求相对不高, 比如链接超时管理等场景, 缺点是, 内存占用相对较高.
@Test
public void test3() throws InterruptedException {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
HashedWheelTimer timer = new HashedWheelTimer(new NamedThreadFactory("timer-task"), 1, TimeUnit.MILLISECONDS,8);
TimerTask timerTask = new TimerTask() {
@Override
public void run(Timeout timeout) throws Exception {
System.out.println("hello world" + LocalDateTime.now().format(formatter));
timer.newTimeout(this, 2, TimeUnit.SECONDS);
}
};
TimerTask timerTask2 = new TimerTask() {
@Override
public void run(Timeout timeout) throws Exception {
float error = 3 / 2;
System.out.println(error);
timer.newTimeout(this, 2, TimeUnit.SECONDS);
}
};
timer.newTimeout(timerTask, 4, TimeUnit.SECONDS);
timer.newTimeout(timerTask2, 4, TimeUnit.SECONDS);
System.out.println("------");
Thread.currentThread().join();
}
执行结果
------
hello world2018-08-30 00:52:26
1.0
hello world2018-08-30 00:52:28
1.0
hello world2018-08-30 00:52:30
1.0
hello world2018-08-30 00:52:32
1.0
HashedWheelTimer源码解读
public HashedWheelTimer(
ThreadFactory threadFactory, // 用来创建worker线程
long tickDuration, // tick的时长,也就是指针多久转一格
TimeUnit unit, // tickDuration的时间单位
int ticksPerWheel, // 一圈有几格
boolean leakDetection // 是否开启内存泄露检测
) {
// 一些参数校验
if (threadFactory == null) {
throw new NullPointerException("threadFactory");
}
if (unit == null) {
throw new NullPointerException("unit");
}
if (tickDuration <= 0) {
throw new IllegalArgumentException("tickDuration must be greater than 0: " + tickDuration);
}
if (ticksPerWheel <= 0) {
throw new IllegalArgumentException("ticksPerWheel must be greater than 0: " + ticksPerWheel);
}
// 创建时间轮基本的数据结构,一个数组。长度为不小于ticksPerWheel的最小2的n次方
wheel = createWheel(ticksPerWheel);
// 这是一个标示符,用来快速计算任务应该呆的格子。
// 我们知道,给定一个deadline的定时任务,其应该呆的格子=deadline%wheel.length.
//但是%操作是个相对耗时的操作,所以使用一种变通的位运算代替:
// 因为一圈的长度为2的n次方,mask = 2^n-1后低位将全部是1,然后deadline&mast == deadline%wheel.length
mask = wheel.length - 1;
// 转换成纳秒处理
this.tickDuration = unit.toNanos(tickDuration);
// 校验是否存在溢出。即指针转动的时间间隔不能太长而导致tickDuration*wheel.length>Long.MAX_VALUE
if (this.tickDuration >= Long.MAX_VALUE / wheel.length) {
throw new IllegalArgumentException(String.format(
"tickDuration: %d (expected: 0 < tickDuration in nanos < %d",
tickDuration, Long.MAX_VALUE / wheel.length));
}
// 创建worker线程
workerThread = threadFactory.newThread(worker);
// 这里默认是启动内存泄露检测:当HashedWheelTimer实例超过当前cpu可用核数*4的时候,将发出警告
leak = leakDetection || !workerThread.isDaemon() ? leakDetector.open(this) : null;
}
//轮盘扫描执行worker
private final Worker worker = new Worker();
//轮盘扫描执行线程
private final Thread workerThread;
//执行线程各种状态
public static final int WORKER_STATE_INIT = 0;
public static final int WORKER_STATE_STARTED = 1;
public static final int WORKER_STATE_SHUTDOWN = 2;
@SuppressWarnings({ "unused", "FieldMayBeFinal", "RedundantFieldInitialization" })
private volatile int workerState = WORKER_STATE_INIT; // 0 - init, 1 - started, 2 - shut down
private final long tickDuration;
private final HashedWheelBucket[] wheel;
private final int mask;
private final CountDownLatch startTimeInitialized = new CountDownLatch(1);
//task入桶之前的队列
private final Queue<HashedWheelTimeout> timeouts = PlatformDependent.newMpscQueue();
//task取消之前的队列
private final Queue<Runnable> cancelledTimeouts = PlatformDependent.newMpscQueue();
private volatile long startTime;
//创建wheel数组
private static HashedWheelBucket[] createWheel(int ticksPerWheel) {
//参数校验, 略
//2的次方
ticksPerWheel = 1;
while (ticksPerWheel < ticksPerWheel) {
ticksPerWheel <<= 1;
}
//初始化
HashedWheelBucket[] wheel = new HashedWheelBucket[ticksPerWheel];
for (int i = 0; i < wheel.length; i++) {
wheel[i] = new HashedWheelBucket();
}
return wheel;
}
//启动Timer, 不需要显示调用, 调用newTimeout时, 会自动调用该方法
public void start() {
//初始为WORKER_STATE_INIT, cas修改为WORKER_STATE_STARTED, 并启动worker线程
switch (WORKER_STATE_UPDATER.get(this)) {
case WORKER_STATE_INIT:
if (WORKER_STATE_UPDATER.compareAndSet(this, WORKER_STATE_INIT, WORKER_STATE_STARTED)) {
workerThread.start();
}
break;
case WORKER_STATE_STARTED:
break;
case WORKER_STATE_SHUTDOWN:
throw new IllegalStateException("cannot be started once stopped");
default:
throw new Error("Invalid WorkerState");
}
//等待worker启动, 并初始化startTime完成
while (startTime == 0) {
try {
startTimeInitialized.await();
} catch (InterruptedException ignore) {
// Ignore - it will be ready very soon.
}
}
}
//停止Timer
public Set<Timeout> stop() {
//worker线程不能调用stop方法, 也就是我们添加的Task中不能调用stop方法.
if (Thread.currentThread() == workerThread) {
throw new IllegalStateException(HashedWheelTimer.class.getSimpleName() + ".stop() cannot be called from "
+ TimerTask.class.getSimpleName());
}
//cas修改状态为shutdown, 如果修改失败, 则当前状态只可能是WORKER_STATE_INIT和WORKER_STATE_SHUTDOWN
if (!WORKER_STATE_UPDATER.compareAndSet(this, WORKER_STATE_STARTED, WORKER_STATE_SHUTDOWN)) {
WORKER_STATE_UPDATER.set(this, WORKER_STATE_SHUTDOWN);//总是设置为WORKER_STATE_SHUTDOWN
return Collections.emptySet();//状态为0和2时, 是没有遗留任务的.
}
//中断worker线程, worker线程中会轮询Timer状态的.
boolean interrupted = false;
while (workerThread.isAlive()) {
workerThread.interrupt();
try {
workerThread.join(100);
} catch (InterruptedException ignored) {
interrupted = true;
}
}
//恢复中断标志
if (interrupted) {
Thread.currentThread().interrupt();
}
//返回未处理的任务
return worker.unprocessedTimeouts();
}
//添加定时任务, delay为延迟时间
public Timeout newTimeout(TimerTask task, long delay, TimeUnit unit) {
start();//未启动, 则启动
//任务先添加到timeouts队列中, 等待下一个tick时, 再添加到对应的wheel中去.
long deadline = System.nanoTime() + unit.toNanos(delay) - startTime;
HashedWheelTimeout timeout = new HashedWheelTimeout(this, task, deadline);
timeouts.add(timeout);
return timeout;
}
}
添加任务时, 并不是直接将人物添加到wheel中, 而是先放入队列, 再等待Worker线程在下一次tick时, 将人物放入wheel中.
HashedWheelTimeout源码解读
//任务的包装类, 链表结构, 负责保存deadline, 轮数, 等
//继承MpscLinkedQueueNode, 是因为timeous队列是MpscLinkedQueue,
//里面对MpscLinkedQueueNode有特殊处理(并发优化)
private static final class HashedWheelTimeout extends MpscLinkedQueueNode<Timeout>
implements Timeout {
private static final int ST_INIT = 0;
private static final int ST_CANCELLED = 1;
private static final int ST_EXPIRED = 2;
private static final AtomicIntegerFieldUpdater<HashedWheelTimeout> STATE_UPDATER;
static {
AtomicIntegerFieldUpdater<HashedWheelTimeout> updater =
PlatformDependent.newAtomicIntegerFieldUpdater(HashedWheelTimeout.class, "state");
if (updater == null) {
updater = AtomicIntegerFieldUpdater.newUpdater(HashedWheelTimeout.class, "state");
}
STATE_UPDATER = updater;
}
private final HashedWheelTimer timer; //timer引用
private final TimerTask task; //要执行的任务引用
private final long deadline; //Timer启动时间 - 任务执行时间(任务加入时间+任务延迟时间)
@SuppressWarnings({"unused", "FieldMayBeFinal", "RedundantFieldInitialization" })
private volatile int state = ST_INIT;
//离任务执行还要等待的轮数, 当任务加入到wheel中时计算该值, 并在Worker中, 每过一轮, 该值减一.
long remainingRounds;
//双链表, 因为只有Worker这一个线程访问, 所以不需要synchronization / volatile.
HashedWheelTimeout next;
HashedWheelTimeout prev;
//HashedWheelTimeout 所在的 wheel
HashedWheelBucket bucket;
HashedWheelTimeout(HashedWheelTimer timer, TimerTask task, long deadline) {
this.timer = timer;
this.task = task;
this.deadline = deadline;
}
@Override
public Timer timer() {
return timer;
}
@Override
public TimerTask task() {
return task;
}
int state = state();
if (state >= ST_CANCELLED) {
// 如果任务已经被取消或者执行了,不能再次取消
}
if (state != ST_IN_BUCKET && compareAndSetState(ST_INIT, ST_CANCELLED)) {
//在进入桶之前执行取消
return true;
}
// 从桶中取消,直接修改task状态
if (!compareAndSetState(ST_IN_BUCKET, ST_CANCELLED)) {
return false;
}
//如果状态修改成功, 则表示第一次调用cancel方法, 将HashedWheelTimeout从bucked中移除的操作封装,
//加入到cancelled队列, 等待下一次tick再移除,
//跟踪下了源码历史发现之所以这么做, 是为了对GC友好, 以前取消任务要等到下一轮才会被处理,
//于是, 改成将cancel的任务放在timeous队列里, 然后统一处理,
//timeous队列是MpscLinkedQueue, 里面对MpscLinkedQueueNode有特殊处理,
//然而, 后面又发现有锁的问题,
//因为timeous这个队列可能被多个线程操作(HashedWheelTimer.newTimeout()), 开始是加锁的,
//于是, 将cancel任务另外存一个队列, 这样, 就不需要使用锁了
timer.timeouts.add(this);
return true;
}
public boolean compareAndSetState(int expected, int state) {
return STATE_UPDATER.compareAndSet(this, expected, state);
}
public int state() {
return state;
}
@Override
public boolean isCancelled() {
return state() == ST_CANCELLED;
}
@Override
public boolean isExpired() {
return state() == ST_EXPIRED;
}
@Override
public HashedWheelTimeout value() {
return this;
}
//到期, 执行任务
public void expire() {
if (!compareAndSetState(ST_INIT, ST_EXPIRED)) {
return;
}
try {
task.run(this);
} catch (Throwable t) {
if (logger.isWarnEnabled()) {
logger.warn("An exception was thrown by " + TimerTask.class.getSimpleName() + '.', t);
}
}
}
}
HashedWheelBucket源码走读
//用来存放HashedWheelTimeout, 结构有点像linked-list, 方便移除操作.
private static final class HashedWheelBucket {
//链表结构
private HashedWheelTimeout head;
private HashedWheelTimeout tail;
//添加HashedWheelTimeout, 链表操作, 不多说~~~
public void addTimeout(HashedWheelTimeout timeout) {
assert timeout.bucket == null;
timeout.bucket = this;
if (head == null) {
head = tail = timeout;
} else {
tail.next = timeout;
timeout.prev = tail;
tail = timeout;
}
}
//当tick到该wheel的时候, Worker会调用这个方法, 根据deadline来判断任务是否过期(remainingRounds是否为0),
//任务到期就执行, 没到期, 就timeout.remainingRounds--, 因为走到这里, 表示改wheel里的任务又过了一轮了.
public void expireTimeouts(long deadline) {
HashedWheelTimeout timeout = head;
//遍历链表
while (timeout != null) {
boolean remove = false;
if (timeout.remainingRounds <= 0) {//任务已到执行点
if (timeout.deadline <= deadline) {
timeout.expire();
} else {
// The timeout was placed into a wrong slot. This should never happen.
throw new IllegalStateException(String.format(
"timeout.deadline (%d) > deadline (%d)", timeout.deadline, deadline));
}
remove = true;
} else if (timeout.isCancelled()) {
remove = true;
} else {//没到期, 剩余轮数减一
timeout.remainingRounds --;
}
//先保存next, 因为移除后, 再获取timeout.next会为空.
HashedWheelTimeout next = timeout.next;
if (remove) {//当以到期, 或者被取消, 就将timeou从链表中移除
remove(timeout);
}
timeout = next;
}
}
//链表移除, 不多说
public void remove(HashedWheelTimeout timeout) {
HashedWheelTimeout next = timeout.next;
if (timeout.prev != null) {
timeout.prev.next = next;
}
if (timeout.next != null) {
timeout.next.prev = timeout.prev;
}
if (timeout == head) {
if (timeout == tail) {
tail = null;
head = null;
} else {
head = next;
}
} else if (timeout == tail) {
tail = timeout.prev;
}
timeout.prev = null;
timeout.next = null;
timeout.bucket = null;
}
//Clear this bucket and return all not expired / cancelled {@link Timeout}s.
public void clearTimeouts(Set<Timeout> set) {
for (;;) {
HashedWheelTimeout timeout = pollTimeout();
if (timeout == null) {
return;
}
if (timeout.isExpired() || timeout.isCancelled()) {
continue;
}
set.add(timeout);
}
}
//链表的poll
private HashedWheelTimeout pollTimeout() {
HashedWheelTimeout head = this.head;
if (head == null) {
return null;
}
HashedWheelTimeout next = head.next;
if (next == null) {
tail = this.head = null;
} else {
this.head = next;
next.prev = null;
}
head.next = null;
head.prev = null;
head.bucket = null;
return head;
}
}
可以看到, 代码也不复杂, 主要是提供一个类似于LinkedList的容器, 用来存放HashedWheelTimeout, 并提供expireTimeouts(long deadline) 方法来处理该wheel中的任务. 具体处理看注释.
Worker源码解读
//主要负责累加tick, 执行到期任务等.
private final class Worker implements Runnable {
private final Set<Timeout> unprocessedTimeouts = new HashSet<Timeout>();
private long tick;
@Override
public void run() {
//初始化startTime, startTime只是一个起始时间的标记, 任务的deadline是相对这个时间点来的.
startTime = System.nanoTime();
//因为nanoTime返回值可能为0, 甚至负数, 所以这时赋值为1, Timer中start方法会判断该值, 直到不为0才跳出循环.
if (startTime == 0) {
startTime = 1;
}
//唤醒阻塞在Timer.start()方法上的线程, 表示已经启动完成.
startTimeInitialized.countDown();
//只要还是启动状态, 就一直循环
do {
//waitForNextTick方法主要是计算下次tick的时间, 然后sleep到下次tick
//返回值就是System.nanoTime() - startTime, 也就是Timer启动后到这次tick, 所过去的时间
final long deadline = waitForNextTick();
if (deadline > 0) {//可能溢出, 所以小于等于0不管
//获取index, 原理见Timer的构造方法注释, 等价于 tick % wheel.length
int idx = (int) (tick & mask);
//移除cancel了的task, 具体可以见HashedWheelTimeout.cancel()方法注释
processCancelledTasks();
//当前tick对应的wheel
HashedWheelBucket bucket = wheel[idx];
//因为添加任务是先加入到timeouts队列中, 而这里就是将任务从队列中取出, 放到对应的bucket中
transferTimeoutsToBuckets();
//见上篇HashedWheelBucket.expireTimeouts()方法的注释
//具体是根据当前的deadline, 判断bucket中的人物是否到期, 到期的任务就执行, 没到期的, 就将人物轮数减一.
//正常情况下, 一个bucket在一轮中, 只会执行一次expireTimeouts方法.
bucket.expireTimeouts(deadline);
//累加tick
tick++;
}
} while (WORKER_STATE_UPDATER.get(HashedWheelTimer.this) == WORKER_STATE_STARTED);
//返回调用stop()时, 还未处理的任务.
for (HashedWheelBucket bucket: wheel) {
bucket.clearTimeouts(unprocessedTimeouts);
}
//加上还没来得及放入bucket中的任务
for (;;) {
HashedWheelTimeout timeout = timeouts.poll();
if (timeout == null) {
break;
}
if (!timeout.isCancelled()) {
unprocessedTimeouts.add(timeout);
}
}
//最好移除下cancel了的task
processCancelledTasks();
}
//将Timer.newTimeout()调用放入到timeouts时的任务放入到对应的bucket中
private void transferTimeoutsToBuckets() {
//一次tick, 最多放入10w任务, 防止太多了, 造成worker线程在这里停留太久.
for (int i = 0; i < 100000; i++) {
HashedWheelTimeout timeout = timeouts.poll();
if (timeout == null) {
//全部处理完了, 退出循环
break;
}
if (timeout.state() == HashedWheelTimeout.ST_CANCELLED) {
//还没加入到bucket中, 就取消了, 继续.
continue;
}
//calculated 表示任务要经过多少个tick
long calculated = timeout.deadline / tickDuration;
//设置任务要经过的轮数
timeout.remainingRounds = (calculated - tick) / wheel.length;
//如果任务在timeouts队列里面放久了, 以至于已经过了执行时间, 这个时候就使用当前tick, 也就是放到当前bucket, 于是方法调用完后就会执行.
final long ticks = Math.max(calculated, tick);
int stopIndex = (int) (ticks & mask);//同样, 类似于ticks % wheel.length
//这时任务所在的bucket在wheel中的位置就表示, 经过n轮后, 还需要多少次tick才执行.
HashedWheelBucket bucket = wheel[stopIndex];
bucket.addTimeout(timeout);//将timeout加入到链表
}
}
//将cancel任务从队列中取出, 并执行cancel操作, 具体可以见HashedWheelTimeout.cancel()方法注释.
private void processCancelledTasks() {
for (;;) {
Runnable task = cancelledTimeouts.poll();
if (task == null) {
// all processed
break;
}
try {
task.run();
} catch (Throwable t) {
if (logger.isWarnEnabled()) {
logger.warn("An exception was thrown while process a cancellation task", t);
}
}
}
}
//sleep, 直到下次tick到来, 然后返回该次tick和启动时间之间的时长
private long waitForNextTick() {
//下次tick的时间点, 用于计算需要sleep的时间
long deadline = tickDuration * (tick + 1);
//循环, 直到HashedWheelTimer被stop, 或者到了下个tick
for (;;) {
//计算需要sleep的时间, 之所以加9999999后再除10000000, 是因为保证为10毫秒的倍数.
final long currentTime = System.nanoTime() - startTime;
long sleepTimeMs = (deadline - currentTime + 999999) / 1000000;
if (sleepTimeMs <= 0) {//小于等于0, 表示本次tick已经到了, 返回.
if (currentTime == Long.MIN_VALUE) {
return -Long.MAX_VALUE; //不懂不懂, 我不懂...估计又是nanoTime的问题.
} else {
return currentTime; //返回过去的时间.
}
}
// Check if we run on windows, as if thats the case we will need
// to round the sleepTime as workaround for a bug that only affect
// the JVM if it runs on windows.
//
// See https://github.com/netty/netty/issues/356
if (PlatformDependent.isWindows()) {//不多说, 一个字, 屌
sleepTimeMs = sleepTimeMs / 10 * 10;
}
try {
Thread.sleep(sleepTimeMs);//睡吧
} catch (InterruptedException ignored) {
//当调用Timer.stop时, 退出
if (WORKER_STATE_UPDATER.get(HashedWheelTimer.this) == WORKER_STATE_SHUTDOWN) {
return Long.MIN_VALUE;
}
}
}
}
public Set<Timeout> unprocessedTimeouts() {
return Collections.unmodifiableSet(unprocessedTimeouts);
}
}
总结
任务里不要有太耗时的操作, 否则会阻塞Worker线程, 导致tick不准.