一、前言
首先有一篇超时任务的实战分析,文章简短精炼明了,阐述了,为什么要用HashedWheelTimer。
https://chuansongme.com/n/1650380646616
看完后,知道了需求,然后,我们再看它的使用。
二、原理
1.概论
(学习一个类,最好的方式是看api文档或源码前的注释,我下载了Netty源码)
这个类用来计划执行非精准的I/O超时。可以通过指定每一格的时间间隔来改变执行时间的精确度。在大多数网络应用中,I/O超时不需要十分准确,因此,默认的时间间隔是100 毫秒,这个值适用于大多数场合。HashedWheelTimer内部结构可以看做是个车轮,简单来说,就是TimerTask的hashTable的车轮。车轮的size默认是512,可以通过构造函数自己设置这个值。注意,当HashedWheelTimer被实例化启动后,会创建一个新的线程,因此,你的项目里应该只创建它的唯一一个实例。
(这个类的实现是源自一位教授的论文 Tony Lauck's paper。算法是代码的灵魂,对于有三五年的工程师来说,用代码实现一个算法并非难事,最难的是算法,怪不得google面试的时候全都考的是算法)
2.结构
下方图示阐述了大致的结构模型(自认为画图有所提升,哈哈)
构造器
1 public HashedWheelTimer( 2 ThreadFactory threadFactory, // 用来创建后台线程 3 long tickDuration, // 每格时间间隔 默认 100
TimeUnit unit, // 时间单位 默认 毫秒
int ticksPerWheel, // 轮子size(一圈多少格) 默认 512 如果不是2的N次方,则去大于该参数的第一个2的N次方
// 理由 便于Hash值的计算
boolean leakDetection, // 泄露检测 默认 true 4 long maxPendingTimeouts) { // 最大待定超时时间 默认 不设置
27 }
在构造方法里对轮子各个参数进行了设置,接下来看工作线程是哪里启动的。先看下面使用HashedWheelTimer的例子(代码摘自SOFARPC源码)
1 if (request.isAsync()) { 2 TIMEOUT_TIMER.newTimeout(new TimerTask() { 3 @Override 4 public void run(Timeout timeout) throws Exception { 5 Map.Entry<ChannelFuture, AbstractHttpClientHandler> entry = responseChannelHandler 6 .removePromise(requestId); 7 if (entry != null) { 8 ClientHandler handler = entry.getValue(); 9 Exception e = timeoutException(request, timeoutMills, null); 10 handler.onException(e); 11 } 12 } 13 14 }, timeoutMills, TimeUnit.MILLISECONDS); 15 }
然后我们进到newTimeOut方法,主要做了三件事,在start()中启动worker线程,然后计算超时时间,最后创建HashedWheelTimeout,丢给队列,并返回:
1 @Override 2 public Timeout newTimeout(TimerTask task, long delay, TimeUnit unit) { ...略18 19 start(); 20 21 // Add the timeout to the timeout queue which will be processed on the next tick. 22 // During processing all the queued HashedWheelTimeouts will be added to the correct HashedWheelBucket. 23 long deadline = System.nanoTime() + unit.toNanos(delay) - startTime; ...29 HashedWheelTimeout timeout = new HashedWheelTimeout(this, task, deadline); 30 timeouts.add(timeout); 31 return timeout; 32 }
我们接着进到worker线程的启动:
1 /** 2 * Starts the background thread explicitly. The background thread will 3 * start automatically on demand even if you did not call this method. 4 * 5 * @throws IllegalStateException if this timer has been 6 * {@linkplain #stop() stopped} already 7 */ 8 public void start() { // 这个方法为什么是public的?我们可以在实例化HashedWheelTimer后,主动调用这个方法,启动worker线程 9 switch (WORKER_STATE_UPDATER.get(this)) { 10 case WORKER_STATE_INIT: 11 if (WORKER_STATE_UPDATER.compareAndSet(this, WORKER_STATE_INIT, WORKER_STATE_STARTED)) { 12 workerThread.start(); // 在这里启动了worker线程 13 } 14 break; 15 case WORKER_STATE_STARTED: 16 break; 17 case WORKER_STATE_SHUTDOWN: 18 throw new IllegalStateException("cannot be started once stopped"); 19 default: 20 throw new Error("Invalid WorkerState"); 21 } 22 23 // Wait until the startTime is initialized by the worker.
// 为了等待worker线程初始化startTime 24 while (startTime == 0) { 25 try { 26 startTimeInitialized.await(); 27 } catch (InterruptedException ignore) { 28 // Ignore - it will be ready very soon. 29 } 30 } 31 }
下一步,我们看看worker线程里面的操作:
首先是初始化startTime,CountDownLatch的触发,
1 @Override 2 public void run() { 3 // Initialize the startTime. 4 startTime = System.nanoTime(); 5 if (startTime == 0) { 6 // We use 0 as an indicator for the uninitialized value here, so make sure it's not 0 when initialized. 7 startTime = 1; 8 } 9 10 // Notify the other threads waiting for the initialization at start(). 11 startTimeInitialized.countDown(); 12 13 do { 14 final long deadline = waitForNextTick(); 15 if (deadline > 0) { 16 int idx = (int) (tick & mask); 17 processCancelledTasks(); 18 HashedWheelBucket bucket = 19 wheel[idx]; 20 transferTimeoutsToBuckets(); 21 bucket.expireTimeouts(deadline); 22 tick++; 23 } 24 } while (WORKER_STATE_UPDATER.get(HashedWheelTimer.this) == WORKER_STATE_STARTED); 25 26 // Fill the unprocessedTimeouts so we can return them from stop() method. 27 for (HashedWheelBucket bucket: wheel) { 28 bucket.clearTimeouts(unprocessedTimeouts); 29 } 30 for (;;) { 31 HashedWheelTimeout timeout = timeouts.poll(); 32 if (timeout == null) { 33 break; 34 } 35 if (!timeout.isCancelled()) { 36 unprocessedTimeouts.add(timeout); 37 } 38 } 39 processCancelledTasks(); 40 }
-------------------------------------------------------------
到此为止!上面各个方法里有太多细节的地方,不是很懂,随便乱说,误导大家,不说,光贴代码,感觉好枯燥,索性,写到这里了。。。大家有兴趣,自己接着看源码分析。
。。。。 to be continued!
主要是记录一下学习这个类的一个过程吧。过程中的东西,比结果更加重要!
有空的话,把HashedWheelTimer 算法的论文,打算看看。
----------------------------------------------------------------------------------------------
参考资源:
https://www.jianshu.com/p/328f22432638