零 前期准备
0 FBI WARNING
文章异常啰嗦且绕弯。
1 版本
JDK 版本 : Adoptopenjdk 14.0.1
IDE : idea 2020.1
Netty 版本 : netty-all 4.1.46.Final
2 HashedWheelTimer 简介
HashedWheelTimer 是 Netty 中实现延迟任务的工具类。
3 Demo
import io.netty.util.HashedWheelTimer;
import io.netty.util.Timeout;
import io.netty.util.TimerTask;
import java.util.concurrent.TimeUnit;
/**
* netty 时间轮 demo
*/
public class TimeWheel {
public static void main(String[] args) {
// 创建时间轮对象
HashedWheelTimer wheel = new HashedWheelTimer();
// 打印当前时间
System.out.println(System.currentTimeMillis());
// 创建一个延迟任务,用于打印
wheel.newTimeout(new TimerTask() {
@Override
public void run(Timeout timeout) throws Exception {
System.out.println("test print " + System.currentTimeMillis());
}
},1000, TimeUnit.MILLISECONDS);
// 线程休眠,防止线程退出
try {
Thread.sleep(1000000000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
4 创建 HashedTWheelTimer
4.1 构造器
上述 demo 中创建 HashedTWheelTimer:
HashedWheelTimer wheel = new HashedWheelTimer();
追踪它的实现:
/**
* 其它所有构造器都最终调用到这个构造器
* threadFactory - 线程工厂,默认是 Executors 中的 defaultThreadFactory
* tickDuration - 时间轮两次 tick 之间的间隔时间,默认值 100
* unit - tick 的时间单位,默认为 毫秒
* ticksPerWheel - 一轮 tick 的数量
* leakDetection - 用于优雅关闭的引用对象,默认 true
* maxPendingTimeouts - 最大任务数,默认 -1,即为不限制
**/
public HashedWheelTimer(
ThreadFactory threadFactory,
long tickDuration,
TimeUnit unit,
int ticksPerWheel,
boolean leakDetection,
long maxPendingTimeouts) {
// 做部分参数的非空和合规验证
ObjectUtil.checkNotNull(threadFactory, "threadFactory");
ObjectUtil.checkNotNull(unit, "unit");
ObjectUtil.checkPositive(tickDuration, "tickDuration");
ObjectUtil.checkPositive(ticksPerWheel, "ticksPerWheel");
// wheel 是一个 HashedWheelBucket 数组,是任务存储器,具体见 4.2
wheel = createWheel(ticksPerWheel);
mask = wheel.length - 1;
// 获取真实的 tick 时间,并检验合法性
long duration = unit.toNanos(tickDuration);
if (duration >= Long.MAX_VALUE / wheel.length) {
throw new IllegalArgumentException(String.format(
"tickDuration: %d (expected: 0 < tickDuration in nanos < %d",tickDuration, Long.MAX_VALUE / wheel.length));
}
// MILLISECOND_NANOS = 1000000
// duration 不得小于 1000000
if (duration < MILLISECOND_NANOS) {
logger.warn("Configured tickDuration {} smaller then {}, using 1ms.",tickDuration, MILLISECOND_NANOS);
this.tickDuration = MILLISECOND_NANOS;
} else {
this.tickDuration = duration;
}
// 工作线程,具体见 4.3
workerThread = threadFactory.newThread(worker);
// 判断是否需要创建一个用于优雅关闭的弱引用对象
leak = leakDetection || !workerThread.isDaemon() ? leakDetector.track(this) : null;
// 最大任务数
this.maxPendingTimeouts = maxPendingTimeouts;
// 时间轮在一个 jvm 进程中的最大初始化数量不得大于 64 个,不然的话会在此处报错
if (INSTANCE_COUNTER.incrementAndGet() > INSTANCE_COUNT_LIMIT &&
WARNED_TOO_MANY_INSTANCES.compareAndSet(false, true)) {
reportTooManyInstances();
}
}
4.2 HashedWheelBucket
wheel 是一个 HashedWheelBucket 数组对象:
private final HashedWheelBucket[] wheel;
wheel 的初始化在 createWheel(...) 方法中进行:
// HashedWheelBucket.class
private static HashedWheelBucket[] createWheel(int ticksPerWheel) {
// ticks 是在创建 HashedWheelBucket 的时候输入的分片数
// 分片数不可小于 0,或者大于 1073741824
if (ticksPerWheel <= 0) {
throw new IllegalArgumentException(
"ticksPerWheel must be greater than 0: " + ticksPerWheel);
}
if (ticksPerWheel > 1073741824) {
throw new IllegalArgumentException(
"ticksPerWheel may not be greater than 2^30: " + ticksPerWheel);
}
// ticks 必须是 2 的倍数,所以此处会对使用者传入的 ticksPerWheel 进行修正
ticksPerWheel = normalizeTicksPerWheel(ticksPerWheel);
// 创建数组并初始化
HashedWheelBucket[] wheel = new HashedWheelBucket[ticksPerWheel];
for (int i = 0; i < wheel.length; i ++) {
wheel[i] = new HashedWheelBucket();
}
// 返回
return wheel;
}
HashedWheelBucket 是 HashedWheelTimer 的内部类:
private static final class HashedWheelBucket {
// HashedWheelBucket 本质上是 HashedWheelTimeout 的链表,而 HashedWheelTimeout 本质上是要执行的任务
private HashedWheelTimeout head;
private HashedWheelTimeout tail;
// ...
}
当使用者存入一个任务的时候,会调用到 HashedWheelBucket 的 addTimeout(...) 方法:
// 本质上是将任务放入到链表中
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;
}
}
而取出任务要消费的时候,调用 pollTimeout() 方法:
// 本质上是将任务从链表中拿出
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;
}
4.3 workerThread
workerThread 是一个线程对象:
private final Thread workerThread;
调用线程工厂的 newThread 方法完成初始化:
workerThread = threadFactory.newThread(worker);
worker 对象是一个 Worker 对象。Worker 是 HashedWheelTimer 的内部类,是 Runnable 的实现类:
private final class Worker implements Runnable {
private final Set<Timeout> unprocessedTimeouts = new HashSet<Timeout>();
private long tick;
@Override
public void run() {
// 记录开始时间
startTime = System.nanoTime();
if (startTime == 0) {
startTime = 1;
}
// CountDownLatch,确保先完成初始化之后才会塞入任务
startTimeInitialized.countDown();
do {
// 在这个方法中线程会休眠,直到下一个 tick 的时间
final long deadline = waitForNextTick();
if (deadline > 0) {
int idx = (int) (tick & mask);
// 清理已经被取消的 task
processCancelledTasks();
// 选定当前要处理的 bucket
HashedWheelBucket bucket = wheel[idx];
// 将储存在队列里的任务放置到对应的 bucket 里
transferTimeoutsToBuckets();
// 执行当前 bucket 里所有的任务
bucket.expireTimeouts(deadline);
// 切换到下一个 tick
tick++;
}
} while (WORKER_STATE_UPDATER.get(HashedWheelTimer.this) == WORKER_STATE_STARTED);
// 后续代码用以优雅关闭
for (HashedWheelBucket bucket: wheel) {
bucket.clearTimeouts(unprocessedTimeouts);
}
for (;;) {
HashedWheelTimeout timeout = timeouts.poll();
if (timeout == null) {
break;
}
if (!timeout.isCancelled()) {
unprocessedTimeouts.add(timeout);
}
}
processCancelledTasks();
}
// 略 ...
}
5 添加任务
回到 demo 的添加任务的代码:
wheel.newTimeout(new TimerTask() {
@Override
public void run(Timeout timeout) throws Exception {
System.out.println("test print " + System.currentTimeMillis());
}
},1000, TimeUnit.MILLISECONDS);
追踪 newTimeout(...) 方法:
// HashedWheelTimer.java
public Timeout newTimeout(TimerTask task, long delay, TimeUnit unit) {
// 非空验证
ObjectUtil.checkNotNull(task, "task");
ObjectUtil.checkNotNull(unit, "unit");
// 任务数量,并与设定的最大值进行比较
long pendingTimeoutsCount = pendingTimeouts.incrementAndGet();
if (maxPendingTimeouts > 0 && pendingTimeoutsCount > maxPendingTimeouts) {
pendingTimeouts.decrementAndGet();
throw new RejectedExecutionException("Number of pending timeouts ("
+ pendingTimeoutsCount + ") is greater than or equal to maximum allowed pending "
+ "timeouts (" + maxPendingTimeouts + ")");
}
// 如果是第一次添加任务,那么在此处会启动任务线程
start();
// 应当被执行的延迟时间
long deadline = System.nanoTime() + unit.toNanos(delay) - startTime;
// 时钟回拨的情况
if (delay > 0 && deadline < 0) {
deadline = Long.MAX_VALUE;
}
// 将 task 包装成一个 timeout 对象
HashedWheelTimeout timeout = new HashedWheelTimeout(this,task,deadline);
// 放到队列里
timeouts.add(timeout);
return timeout;
}
本文仅为个人的学习笔记,可能存在错误或者表述不清的地方,有缘补充
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。