携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第3天,点击查看活动详情
HashedWheelTimer的结构
Queue<HashedWheelTimeout> timeouts
提交任务的队列(生产者消费者模式).Thread workerThread
执行任务线程Worker worker
首先了Runnable,被workerThread所执行startTime
代表实时间轮转动的开始时间HashedWheelBucket[] wheel
存储时间轮的数组HashedWheelBucket
为一个双向链表,存储该时间刻度的具体任务mask
为wheel数组长度减一,用于延迟任务进行位置定位的关键属性.
startTime是如何初始化的.
CountDownLatch startTimeInitialized = new CountDownLatch(1);
用于时间初始化
startTime是由workerThread一步初始化,并通过CountDownLatch进行唤醒等待的线程;提交任务执行newTimeout(),再该方法中会调用start()方法进行workerThread线程启动,并等待期初始化完成.
public void start() {
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");
}
// Wait until the startTime is initialized by the worker.
while (startTime == 0) {
try {
startTimeInitialized.await();
} catch (InterruptedException ignore) {
// Ignore - it will be ready very soon.
}
}
}
再WorkThread线程中会执行Work的run方法初始化时间.
private final class Worker implements Runnable {
private final Set<Timeout> unprocessedTimeouts = new HashSet<Timeout>();
private long tick;
@Override
public void run() {
// Initialize the startTime.
startTime = System.nanoTime();
if (startTime == 0) {
// We use 0 as an indicator for the uninitialized value here, so make sure it's not 0 when initialized.
startTime = 1;
}
}
任务提交
当我们提交一个延迟任务时,会计算一个相对于startTime的相对执行时间.此处的时间统一以纳秒进行计算.
long deadline = System.nanoTime() + unit.toNanos(delay) - startTime;
任务添加到队列中.
HashedWheelTimeout timeout = new HashedWheelTimeout(this, task, deadline);
timeouts.add(timeout);
HashedWheelTimeout任务状态
- ST_INIT 初始化 默认状态
- ST_CANCELLED 取消状态
- ST_EXPIRED 已过时状态
只有当任务状态ST_INIT时,手动调用cancel(),任务变为ST_CANCELLED; 只有当任务状态ST_INIT时,手动调用expire,任务变为ST_EXPIRED,任务执行后,也会变为ST_EXPIRED. 上述方法操作,都是基于CAS来保证原子性.
if (!compareAndSetState(ST_INIT, ST_EXPIRED)) {
return;
}
public boolean compareAndSetState(int expected, int state) {
return STATE_UPDATER.compareAndSet(this, expected, state);
}
计算休眠时间waitForNextTick
首先根据时间刻度计算摆动到下一个刻度的相对(相对于startTime)时间deadline(纳秒), 换算当前时间与startTime的相对时间currentTime.由于Thread.sleep()的时间为毫秒,所以需要将时间换算为毫秒(10^6),此处加999999的原因是tick在上一轮循环执行完后就自增过了.
private long waitForNextTick() {
long deadline = tickDuration * (tick + 1);
for (;;) {
final long currentTime = System.nanoTime() - startTime;
long sleepTimeMs = (deadline - currentTime + 999999) / 1000000;
if (sleepTimeMs <= 0) {
if (currentTime == Long.MIN_VALUE) {
return -Long.MAX_VALUE;
} else {
return currentTime;
}
}
if (PlatformDependent.isWindows()) {
sleepTimeMs = sleepTimeMs / 10 * 10;
if (sleepTimeMs == 0) {
sleepTimeMs = 1;
}
}
try {
Thread.sleep(sleepTimeMs);
} catch (InterruptedException ignored) {
if (WORKER_STATE_UPDATER.get(HashedWheelTimer.this) == WORKER_STATE_SHUTDOWN) {
return Long.MIN_VALUE;
}
}
}
}
延迟任务计算存储位置
private void transferTimeoutsToBuckets() {
// transfer only max. 100000 timeouts per tick to prevent a thread to stale the workerThread when it just
// adds new timeouts in a loop.
for (int i = 0; i < 100000; i++) {
HashedWheelTimeout timeout = timeouts.poll();
if (timeout == null) {
// all processed
break;
}
if (timeout.state() == HashedWheelTimeout.ST_CANCELLED) {
// Was cancelled in the meantime.
continue;
}
long calculated = timeout.deadline / tickDuration;
timeout.remainingRounds = (calculated - tick) / wheel.length;
final long ticks = Math.max(calculated, tick); // Ensure we don't schedule for past.
int stopIndex = (int) (ticks & mask);
HashedWheelBucket bucket = wheel[stopIndex];
bucket.addTimeout(timeout);
}
}
- 每次最多取出100k的任务.
- 根据任务的相对时间timeout.deadline除以时间轮刻度大小,得到时钟需要摆动多少次才能执行该任务.
- 由于在任务添加过程中,始终也会摆动,所以此处减去已经摆动的次数tick; 根据差值计算圈数remainingRounds.
- 为了避免任务的延迟时间已过,会将已过时的任务放在当前的刻度上执行.
- 计算该任务对应数组的位置.病添加到该刻度对应的双向链表中.
任务执行
do {
final long deadline = waitForNextTick();
if (deadline > 0) {
int idx = (int) (tick & mask);
processCancelledTasks();
HashedWheelBucket bucket =
wheel[idx];
transferTimeoutsToBuckets();
bucket.expireTimeouts(deadline);
tick++;
}
} while (WORKER_STATE_UPDATER.get(HashedWheelTimer.this) == WORKER_STATE_STARTED);
在WorkThread线程中,只要当前状态为WORKER_STATE_STARTED,就一直运行该循环. bucket.expireTimeouts(deadline);
进行任务执行操作
public void expireTimeouts(long deadline) {
HashedWheelTimeout timeout = head;
while (timeout != null) {
HashedWheelTimeout next = timeout.next;
if (timeout.remainingRounds <= 0) {
next = remove(timeout);
if (timeout.deadline <= deadline) {
timeout.expire();
} else {
throw new IllegalStateException(String.format(
"timeout.deadline (%d) > deadline (%d)", timeout.deadline, deadline));
}
} else if (timeout.isCancelled()) {
next = remove(timeout);
} else {
timeout.remainingRounds --;
}
timeout = next;
}
}
- 从双向链表的head节点开始循环取出任务,如果remainingRounds(圈数小于等于0)则从链表中移除该任务,则执行任务.
- 移除取消的任务.
timeout.isCancelled()
- remainingRounds 递减.
tips
startTime属性是否必须添加volatile.
众所周知volatile是java里面用来保证可见性的关键字.那么对于volatile的具体原理,此处不在详解.对应happens-before相对应的几条规则.
- 程序顺序规则:一个线程中的每个操作,happens-before于该线程中的任意后续操作
- 监视器锁规则:对一个锁的解锁,happens-before于随后对这个锁的加锁。
- volatile变量规则:对一个volatile域的写,happens-before于任意后续对这个volatile域的读
- 传递性:如果A happens-before B,且B happens-before C,那么A happens-before C。
- start()规则:如果线程A执行操作ThreadB.start()(启动线程B),那么A线程的ThreadB.start()操作happens-before于线程B中的任意操作。
- Join()规则:如果线程A执行操作ThreadB.join()并成功返回,那么线程B中的任意操作happens-before于线程A从ThreadB.join()操作成功返回。
由于CoutDownLatch保证了原子性.所以对应规则2,结合规则1.4来看.由于A happens-before B且B happens-before C,所以A happens-before C的.所以workThread对于startTime的修改必定能保证任务提交线程对startTime的读可见.
内存检测检测 ResourceLeakTracker
关于内存泄漏这块逻辑,本人目前暂时不太熟悉.但是能体会到netty作者的设计思想,因为在netty中存在很多的内存池.要想不发生OOM,那么必定存在一套完善的监控.由此可见netty是非常值得深入研究的. 我们看到的可能只是沧海一粟,要学得还很多.
结语:心无惧,方能无敌于天下.