📕 引言
定时器是什么?
定时器也是软件开发中的一个重要组件. 类似于一个 “闹钟”. 达到一个设定的时间之后, 就执行某个指定好的代码
定时器是一种实际开发中非常常用的组件.比如网络通信中, 如果对方 500ms 内没有返回数据, 则断开连接尝试重连.比如一个 Map, 希望里面的某个 key 在 3s 之后过期(自动删除).
类似于这样的场景就需要用到定时器
🌳 Java标准库中的定时器
-
标准库中提供了一个 Timer 类. Timer 类的核心方法为 schedule .
-
schedule 包含两个参数.
-
第一个参数指定即将要执行的任务代码,不是Runnable,而是Timer Task(其实也是实现了Runnable)
-
第二个参数指定多长时间之后执行 (单位为毫秒),以当前执行 schedule 的时刻为基准,继续等 delay 时间之后再去进一步执行
代码:
🎄 模拟实现定时器
🚩 定时器的构成
1,一个带优先级的阻塞队列
- 为啥要带优先级呢?
- 因为阻塞队列中的任务都有各自的执行时刻 (delay). 最先执行的任务一定是 delay 最小的. 使用带优先级的队列就可以高效的把这个 delay 最小的任务找出来.
- 队列中的每个元素是一个 MyTask 对象.
2,MyTask 中带有一个时间属性, 队首元素就是即将要执行的任务
3,同时有一个线程一直扫描队首元素, 看队首元素是否需要执行
📌 实现MyTimerTask类
定义一个类表示一个任务(基于Runnable)
📌 实现MyTimer类
通过一定的数据结构,保存多个任务
直观的做法使用List:如果这里的元素很多,后序用来执行任务的线程,就需要不停的循环扫描这里的任务,分别判定是否要执行,循环扫描的开销就很大
使用优先级队列:可以把这些任务通过优先级队列保存起来,按照时间作为优先级的先后标准,就可以做到队首元素就是时间最靠前的任务,扫描线程只需要关注队首元素即可(时间最靠前的任务都没到点,剩下的任务就更不会到点了)。
需要一个线程来负责执行这里的任务(在指定时间去执行)
由于优先级队列本身就不是线程安全的,在MyTimer类中,调用 schedule 是一个线程,入队列操作。红色方框中的扫描线程是另一个线程,出队列操作。这时候很明显就是两个线程来针对同一个队列进行操作,此时就需要进行加锁。
注意:直接使用阻塞队列是否就线程安全了?下面讲到
加锁:
在入队列的时候先加锁,再去入队列。针对while循环也进行加锁。
📌 解决上述存在的问题
上述代码到这里呢,还存在三个比较核心的问题:
1,try-catch
当前代码中并没有阻塞等待,加了这个也没有用,反而报错了!那么上述扫描线程的代码是否要有阻塞等待??
所以这里的else就不适合写continue,使用wait来进行控制,但是不能死等,等待时间为任务时间减去当前时间即可。
那么对于判定队列是否为空,也可进行wait
2,使用阻塞队列问题:
3,我们要把任务对象,通过优先级队列管理起来,那么啥样的对象能放进优先级队列,是随便写个对象吗?
对于优先级队列,我们讲过,元素之间要根据什么进行比较,所以对于自己实现的类要实现比较器,手动的去规定比较规则。
完整代码:
测试: