手撸单级和多级时间轮
为了解决比较高要求的延时场景就有了时间轮模型。下面,来动手实现一个单级时间轮和一个两级时间轮。
前言
延时或定时任务在开发中十分常见,Java
也内置了Timer
工具类来实现延时或定时功能,Timer
通过优先队列来维护延时任务,以延时任务的到期时间作为优先级,通过单线程循环地从队列中取出到期的任务执行,延时通过wait
方法来实现。
Timer
实现的延时功能在一些简单的场景是可以的,但是对于一些要求比较高的场景则有些不够看了。因为通过优先级队列来维护延时任务,添加和获取操作的时间复杂度都是O(logn)
,在任务多的情况下,效率是比较低的。
并且,通过单线程来监听和处理到期的延时任务,如果某个任务执行超时,那么将导致后面的延时任务都推迟执行。所以,为了解决比较高要求的延时场景就有了时间轮模型。下面,来动手实现一个单级时间轮和一个两级时间轮。
时间轮
时间轮可以以O(1)
的时间复杂度添加和取出到期的延时任务,在执行效率上比优先级队列要高。
单级时间轮
原理:
通过一个数组来存储延时任务,当多个延时任务在同一个时刻执行时,组成一个双向链表(为了O(1)
地添加新的延时任务)。再通过一个指针每tick
时间循环地后移,每到达一个位置时,就执行该位置下地延时任务。
多级时间轮
为什么需要多级时间轮?
因为单级时间轮的最大延时时间比较小(受数组元素个数限制),为了适用更多的场景,需要扩大数组大小,但那样的话内存消耗很大且可能放不下,所以有了多级时间轮。
多级时间轮,比如建立以秒,分钟,小时为单位的三级时间轮,那么该时间轮最大可以延时一天。不过,多级时间轮也存在一些问题:
(1)当始末时刻时,比如0s(60s)
时,需要从下一级的分钟级时间轮中加载下一分钟的延时任务信息到秒级时间轮。如果延时任务比较多,那么必然会导致一定的延迟
(2)多级时间轮同样也会占用比较大的内存,或者内存存不下
解决办法其实也不难,在始末时刻到来之前,先预加载未来一分钟或者一小时的延时任务信息就可以避免加载的延迟。而对于内存存储不下所有的延时任务,那么可以考虑将实际的延时任务存储到外存,而内存只存储索引信息,这样就可以处理很多延时任务的情况下,内存不够用的问题。
实现源码
源码请看GitHub
仓库:
github – Flowers-bloom – TimeWheel
如果访问速度太慢,可访问Gitee
:
gitee – flowers-bloom – TimeWheel