平衡吊叉(netty系列之:HashedWheelTimer一种定时器的高效实现)

Posted

篇首语:博学之,审问之,慎思之,明辨之,笃行之。本文由小常识网(cha138.com)小编为大家整理,主要介绍了平衡吊叉(netty系列之:HashedWheelTimer一种定时器的高效实现)相关的知识,希望对你有一定的参考价值。

平衡吊叉(netty系列之:HashedWheelTimer一种定时器的高效实现)

简介

定时器是一种在实际的应用中非常常见和有效的一种工具,其原理就是把要执行的任务按照执行时间的顺序进行排序,然后在特定的时间进行执行。JAVA提供了java.util.Timer和java.util.concurrent.ScheduledThreadPoolExecutor等多种Timer工具,但是这些工具在执行效率上面还是有些缺陷,于是netty提供了HashedWheelTimer,一个优化的Timer类。

一起来看看netty的Timer有何不同吧。

java.util.Timer

Timer是JAVA在1.3中引入的。所有的任务都存储在它里面的TaskQueue中:

private final TaskQueue queue = new TaskQueue();

TaskQueue的底层是一个TimerTask的数组,用于存储要执行的任务。

private TimerTask[] queue = new TimerTask[128];

看起来TimerTask只是一个数组,但是Timer将这个queue做成了一个平衡二叉堆。

当添加一个TimerTask的时候,会插入到Queue的最后面,然后调用fixup方法进行再平衡:

    void add(TimerTask task)         // Grow backing store if necessary        if (size + 1 == queue.length)            queue = Arrays.copyOf(queue, 2*queue.length);        queue[++size] = task;        fixUp(size);    

当从heap中移出运行的任务时候,会调用fixDown方法进行再平衡:

    void removeMin()         queue[1] = queue[size];        queue[size--] = null;  // Drop extra reference to prevent memory leak        fixDown(1);    

fixup的原理就是将当前的节点和它的父节点进行比较,如果小于父节点就和父节点进行交互,然后遍历进行这个过程:

    private void fixUp(int k)         while (k > 1)             int j = k >> 1;            if (queue[j].nextExecutionTime <= queue[k].nextExecutionTime)                break;            TimerTask tmp = queue[j];  queue[j] = queue[k]; queue[k] = tmp;            k = j;            

fixDown的原理是比较当前节点和它的子节点,如果当前节点大于子节点,则将其降级:

    private void fixDown(int k)         int j;        while ((j = k << 1) <= size && j > 0)             if (j < size &&                queue[j].nextExecutionTime > queue[j+1].nextExecutionTime)                j++; // j indexes smallest kid            if (queue[k].nextExecutionTime <= queue[j].nextExecutionTime)                break;            TimerTask tmp = queue[j];  queue[j] = queue[k]; queue[k] = tmp;            k = j;            

二叉平衡堆的算法这里不做详细的介绍。大家可以自行查找相关的文章。

java.util.concurrent.ScheduledThreadPoolExecutor

虽然Timer已经很好用了,并且是线程安全的,但是对于Timer来说,想要提交任务的话需要创建一个TimerTask类,用来封装具体的任务,不是很通用。

所以JDK在5.0中引入了一个更加通用的ScheduledThreadPoolExecutor,这是一个线程池使用多线程来执行具体的任务。当线程池中的线程个数等于1的时候,ScheduledThreadPoolExecutor就等同于Timer。

ScheduledThreadPoolExecutor中进行任务保存的是一个DelayedWorkQueue。

DelayedWorkQueue和DelayQueue,PriorityQueue一样都是一个基于堆的数据结构。

因为堆需要不断的进行siftUp和siftDown再平衡操作,所以它的时间复杂度是O(log n)。

下面是DelayedWorkQueue的shiftUp和siftDown的实现代码:

       private void siftUp(int k, RunnableScheduledFuture<?> key)             while (k > 0)                 int parent = (k - 1) >>> 1;                RunnableScheduledFuture<?> e = queue[parent];                if (key.compareTo(e) >= 0)                    break;                queue[k] = e;                setIndex(e, k);                k = parent;                        queue[k] = key;            setIndex(key, k);                private void siftDown(int k, RunnableScheduledFuture<?> key)             int half = size >>> 1;            while (k < half)                 int child = (k << 1) + 1;                RunnableScheduledFuture<?> c = queue[child];                int right = child + 1;                if (right < size && c.compareTo(queue[right]) > 0)                    c = queue[child = right];                if (key.compareTo(c) <= 0)                    break;                queue[k] = c;                setIndex(c, k);                k = child;                        queue[k] = key;            setIndex(key, k);        

HashedWheelTimer

因为Timer和ScheduledThreadPoolExecutor底层都是基于堆结构的。虽然ScheduledThreadPoolExecutor对Timer进行了改进,但是他们两个的效率是差不多的。

那么有没有更加高效的方法呢?比如O(1)是不是可以达到呢?

我们知道Hash可以实现高效的O(1)查找,想象一下假如我们有一个无限刻度的钟表,然后把要执行的任务按照间隔时间长短的顺序分配到这些刻度中,每当钟表移动一个刻度,即可以执行这个刻度中对应的任务,如下图所示:

这种算法叫做Simple Timing Wheel算法。

但是这种算法是理论上的算法,因为不可能为所有的间隔长度都分配对应的刻度。这样会耗费大量的无效内存空间。

所以我们可以做个折中方案,将间隔时间的长度先用hash进行处理。这样就可以缩短间隔时间的基数,如下图所示:

这个例子中,我们选择8作为基数,间隔时间除以8,余数作为hash的位置,商作为节点的值。

每次遍历轮询的时候,将节点的值减一。当节点的值为0的时候,就表示该节点可以取出执行了。

这种算法就叫做HashedWheelTimer。

netty提供了这种算法的实现:

public class HashedWheelTimer implements Timer 

HashedWheelTimer使用HashedWheelBucket数组来存储具体的TimerTask:

private final HashedWheelBucket[] wheel;

首先来看下创建wheel的方法:

    private static HashedWheelBucket[] createWheel(int ticksPerWheel)         //ticksPerWheel may not be greater than 2^30        checkInRange(ticksPerWheel, 1, 1073741824, "ticksPerWheel");        ticksPerWheel = normalizeTicksPerWheel(ticksPerWheel);        HashedWheelBucket[] wheel = new HashedWheelBucket[ticksPerWheel];        for (int i = 0; i < wheel.length; i ++)             wheel[i] = new HashedWheelBucket();                return wheel;    

我们可以自定义wheel中ticks的大小,但是ticksPerWheel不能超过2^30。

然后将ticksPerWheel的数值进行调整,到2的整数倍。

然后创建ticksPerWheel个元素的HashedWheelBucket数组。

这里要注意,虽然整体的wheel是一个hash结构,但是wheel中的每个元素,也就是HashedWheelBucket是一个链式结构。

HashedWheelBucket中的每个元素都是一个HashedWheelTimeout. HashedWheelTimeout中有一个remainingRounds属性用来记录这个Timeout元素还会在Bucket中保存多久。

long remainingRounds;

总结

netty中的HashedWheelTimer可以实现更高效的Timer功能,大家用起来吧。

更多内容请参考 http://www.flydean.com/50-netty-hashed-wheel-timer/

最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!

欢迎关注我的公众号:「程序那些事」,懂技术,更懂你!

相关参考

水位尺读数(掌中宝系列之液位测量及仪表)

...式:H=[L*(ρ1-ρ3)*g-ΔP]/(ρ2-ρ3)g 然后用H减去水位零点相对平衡容器下取样点的距离,得到的值就是修正后的汽包水位。L为平衡容器两个取样管间高度(m) ρ1为凝结水密度(kg/m3) ρ2为饱和水密度(kg/m3) ρ3为饱和蒸汽密度(kg/m3...

强力压球机原理(高中物理系列模型之对象模型5弹簧模型)

一、模型界定本模型是由弹簧连接的物体系统中关于平衡的问题、动力学过程分析的问题、功能关系的问题,但不包括瞬时性的问题。由弹性绳、橡皮条连接的物体系统也归属于本模型的范畴.二、模型破解1.由胡克定律结合平...

有无的无怎么(强信心·开新局|新时代十年伟大变革奠定坚实基础——新征程上满怀信心开新局展新貌系列述评之四)

...点。跨越10个春秋,我国经济实力实现历史性跃升,发展平衡性、协调性、可持续性明显增强,“稳”的基础持续巩固,“进”的动能不断集聚,迈上更高质量、更有效率、更加公平、更可持续、更为安全的发展之路。新征程上...

日野700平衡轴座(十年依旧是“新车”——进口日野700系列FY 8X4二类底盘实拍(下))

上一篇文章里介绍的外观和驾驶室部分相信大家都历历在目,但好看的也只是皮囊,一部好车好与不好更关键的在于其是否具备优秀,高效且可靠的动力链以及底盘结构。今天就一起走近日野700FY的车架以及动力部分。日野的旗...

手电(短小精悍的性能均衡之作傲雷神剑Javelot mini远射战术手电体验)

...能。那么,一款兼顾便携,同时又能有着出色远射能力的平衡款就被摆上了日程。新出的神剑mini就完美契合了用户这一需求。不大的个头足以塞进裤兜、口袋。

平衡性思维者((六)现实理性人的认知体系之:什么是自我的平衡性?)

...会做出相应的反应,主体意志工作时只遵循一个原则——平衡性!那么,什么是平衡性?当我大脑中什么也不想时,“我”处于一种平衡状态中,既没有思维,也不会有情绪。突然,肚子里传来不好的信息,大脑经过加工得出结...

悬挂弹簧平衡器(从50吨级的T1到77吨级的M6A2E1,美军冷门重坦M6系列大解析)

西班牙内战后,各国都认识到了轻型坦克的不足,转而发展性能更加均衡的中型或者是重型坦克去了,然而身处北美大陆的美国却不以为然,继续魔改着自己的M3/M5轻型坦克系列,重型坦克的研制被束之高阁,二战爆发后美国人...

扫地清洗路面车(石头自清洁扫拖机器人 G10 Plus评测:平衡加码的清洁新选择)

早些时候,我们见到了实力强劲的石头自清洁扫拖机器人G10S系列,彼时我们还在感慨,本身口碑就就不错的石头G10系列有了这么一款实力强大的继任者,未来的市场表现应该会更加出色。不过今天我们该暂时把G10S系列暂时遮一...

平衡器(花果山是天地之柱,孙悟空是天地平衡器,老君是人参果真正的主人)

《西游记》解读之七十九太上老君,在西游记里是开天辟地的祖师,是先天地而生的所有仙神佛人鬼的祖宗。所以凡是开天地时候的宝贝,基本都在他手里,比如芭蕉扇。按照这个逻辑,人参果树也应该是他的。他用过的东西,...

汽油叉车(斗山推出高燃油效率的9系重型内燃平衡重叉车)

  斗山,皮实耐用的高性能叉车制造商。该公司在近日推出了9系列叉车的最新成员,即重载的D160S-9系列,采用斗山全新的世界级DL06V(P)欧洲第五阶段兼容的内燃发动机。  在10.0-16.0吨的容量范围内有五种型号,这些动力强...