文章目录
- 一.HashedWheelTimer是什么?
- 二.能干什么?为什么需要这个东西?
- 优点
- 适用场景
- 三.怎么用?使用步骤
- 1.引入pom
- 2.使用举例
- 四.时间轮原理
- 五.使用注意点
- 1.一个HashedWheelTimer对象只有一个worker线程
- 2.每次添加的任务只会执行一次
- 3.时间轮的参数非常重要
- 4.所有的任务都是顺序串行执行的
一.HashedWheelTimer是什么?
时间轮是一种非常惊艳的数据结构。其在Linux内核中使用广泛,是Linux内核定时器的实现方法和基础之一。
换句话说时间轮是一种高效来利用线程资源来进行批量化调度的一种调度模型。把大批量的调度任务全部都绑定到同一个的调度器上面,使用这一个调度器来进行所有任务的管理(manager),触发(trigger)以及运行(runnable)。能够高效的管理各种延时任务,周期任务,通知任务等等
而HashedWheelTimer则是使用了时间轮这种数据结构,它是Netty内部的一个工具类,最开始主要用来优化I/O超时的检测,本文将详细分析HashedWheelTimer的使用及原理。
二.能干什么?为什么需要这个东西?
优点
其实笔者认为其最大的优点就是可以在一个线程中动态的添加定时(延时)任务
像我们经常使用Timer,ScheduledExecutorService,Spring的Scheduled这些都是无法做到这一点的,一旦某个线程开始执行某个定时任务,都是无法再去动态添加的
那某些场景,比如说有很多小的定时任务,难道每一个都去起一个线程处理吗?那数量多的话对程序势必影响很大,浪费资源,这个时候就可以考虑HashedWheelTimer了.
而在netty中,因为其可能管理上百万的连接,每一个连接都会有很多超时任务。比如发送超时、心跳检测间隔等,如果每一个定时任务都启动一个Timer,不仅低效,而且会消耗大量的资源。所以创造了这个工具类.
适用场景
- 订单超时
- 分布式锁中为线程续期的看门狗
- 心跳检测
三.怎么用?使用步骤
1.引入pom
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.45.Final</version>
</dependency>
2.使用举例
1.构建对象,添加定时任务
//在一个格子里面的并不会区分的很细,而会依次顺序执行,所以适用于对时间精度要求不高的任务
//构建时间轮对象
HashedWheelTimer timer = new HashedWheelTimer(5, TimeUnit.SECONDS, 10);
//添加定时任务1,延迟2s执行
timer.newTimeout((TimerTask) timeout -> {
System.out.println("任务1执行");
System.out.println("线程名称:"+Thread.currentThread().getName());
},2,TimeUnit.SECONDS);
//添加定时任务2,延迟2s执行
timer.newTimeout((TimerTask) timeout -> {
System.out.println("任务2执行");
System.out.println("线程名称:"+Thread.currentThread().getName());
},5,TimeUnit.SECONDS);
//等待定时任务执行完毕后,将时间轮内部工作线程停止,这里只是粗略的等待,也可以使用CountDownLatch
Thread.sleep(10000);
timer.stop();
2.取消某个定时任务
//构建时间轮对象
HashedWheelTimer timer = new HashedWheelTimer(1, TimeUnit.SECONDS, 10);
//添加定时任务1
Timeout newTimeout = timer.newTimeout((TimerTask) timeout -> {
System.out.println("任务1执行");
System.out.println("线程名称:" + Thread.currentThread().getName());
}, 5, TimeUnit.SECONDS);
//现在又想取消掉这个任务
if(!newTimeout.isExpired()){
newTimeout.cancel();
}
四.时间轮原理
时间轮其实就是一种环形的数据结构,可以想象成时钟,分成很多格子,一个格子代码一段时间(这个时间越短,Timer的精度越高)。并用一个链表表示在该格子上的到期任务,同时一个指针随着时间一格一格转动,并执行相应格子中的到期任务。任务通过取摸决定放入那个格子。如下图所示:
假设一个格子是1秒,则整个wheel能表示的时间段为8s,假如当前指针指向2,此时需要调度一个3s后执行的任务,显然应该加入到(2+3=5)的方格中,指针再走3次就可以执行了;如果任务要在10s后执行,应该等指针走完一个round零2格再执行,因此应放入4,同时将round(1)保存到任务中。检查到期任务时应当只执行round为0的,格子上其他任务的round应减1。
再回头看看构造方法的三个参数分别代表
- tickDuration
每一tick的时间 - timeUnit
tickDuration的时间单位 - ticksPerWheel
就是轮子一共有多个格子,即要多少个tick才能走完这个wheel一圈。
五.使用注意点
1.一个HashedWheelTimer对象只有一个worker线程
2.每次添加的任务只会执行一次
3.时间轮的参数非常重要
比如我这里设置每个格子的时间为6s,添加了两个定时任务,一个延时2s执行,一个延时5s执行,但是最终执行结果是同时执行,因为这两个任务都被分配到第一个格子中,按顺序执行.
所以时间轮的参数要根据时间情况具体设定
4.所有的任务都是顺序串行执行的
也就是说上一个任务的异常延时会影响到下一个任务.
比如我这里添加了两个定时任务,第一个延时2s,第二个延时5s,但是因为第一个任务的延时,导致第二个延时了10s才执行.
所以这里要求时间轮执行的任务都是比较快的, 或者这里可以使用异步任务去处理.
今天的分享就到这里了,有问题可以在评论区留言,均会及时回复呀.
我是bling,未来不会太差,只要我们不要太懒就行, 咱们下期见.