什么是定时器
定时器是软件开发中的一个重要组件,类似于一个“闹钟”。达到一个设定的时间之后,就执行某个指定好的代码。
标准库中的定时器
- 标准库中提供了一个 Timer 类。Timer 类的核心方法为 schedule。
- schedule 包含两个参数。第一个参数指定即将要执行的任务代码,第二个参数指定多长时间之后执行(ms)。
正常描述任务,是 Runnable。
在定时器这里稍微特殊一点,把 Runnable 封装了一下 TimerTask。
核心还是重写 run 方法。
我们发现,这个进程也没有结束,其实这个和线程池一样,Timer 中也包含前台线程,阻止进程结束。
模拟实现定时器
1.创建一个类,表示一个任务。
2.定时器中,能够管理多个任务。(必须使用一些集合类把这多个任务管理起来 -> 当管理多个任务的时候,需要确保,时间最早的任务,最先执行。通过遍历的方式,找到时间最早 -> 优先级队列)
在计算中,时间戳来表示时刻
3.实现 schedule 方法,把任务添加到队列中即可。
4.额外创建一个线程,负责执行队列中的任务。
按刚刚的思路写出来就是这样啦,通过测试案例,也能运行出来
但是,一旦各位按照这个代码运行这个程序,风扇就会疯狂转。->cpu温度升高 -> cpu在高负荷工作。
原因在于这里面还有一些细节没有注意。
- 首先,咱们的队列,添加完 3 个元素后,队列就空了。这里的转,其实是在等待新的任务到来,明明是等,却需要消耗大量的cpu资源,不太科学的。
有的人可能会想到在if代码块中添加sleep。
但是,这里的sleep时间不好设定,如果设置的短,起不到解决忙等的效果。如果设置的长,万一有新的任务来了,又没法及时处理。
所以,同样是等待,我们这里采用 wait ,notify 来解决这个问题。
那什么时候唤醒 呢? -> 每当有新任务进入队列时,唤醒。
- 另外,这一块代码也会使进程陷入忙等
我们也是采用wait的方法解决问题。
但是,此处这个wait必须得有其他线程 schedule 才能唤醒,实际上,此处是因该时间到,就继续执行的。
可能还是有人想用sleep,那我们来举个例子:本来队首是 14:30分,咱们在14:10分添加一个新的任务 14:20来执行。如果刚才是sleep,sleep 30分钟 无法被唤醒,意味着14:20的任务(新任务)无法及时执行了,因为sleep的时候,不会释放锁的!另一个线程调用schedule阻塞在枷锁的逻辑上。
标准库提供的 Timer 和刚才写的 MyTimer 差不多,都是使用一个线程,负责扫描队首元素,并执行的。如果任务少或任务的时间分散都无所谓,如果任务特别多,时间非常集中,一个线程就可能执行不过来,那我们完全可以创建多个线程,负责执行这里的队列中的任务,一个线程负责扫描,扫描到需要执行的任务,添加到另一个线程池的任务队列中,由多个线程负责执行。
这样的操作,就创建了一个带有线程池的定时器。
定时器,除了基于 堆(优先级队列) 方式来实现的定时器之外。还有一种方案,基于“时间轮”。类似搞个循环队列(数组),每个元素是一个“时间单位”,每个元素是一个链表,每到一个时间单位,光标指向下一个元素,同时把这个元素对应链表中的任务都执行一遍。它的优势是性能高,但劣势是时间精度不如优先级队列(优先级队列,最大的问题是堆的调整,logN)。(这个是更适合任务特别多的情况)。在这我们就不自己实现了,想了解的话大家可以搜一搜。
由于定时器,是一个非常重要的组件,在分布式系统中,把定时器专门提取出来,封装成一个单独的服务器(和消息队列很像)。
分布式系统,有很多服务器。
如果只是一个类,意味着所有的服务器都需要执行这一套同样的逻辑,比较复杂,如果进行调整,所有的都要改。
提取出来,独立的服务器,大家都去用,如果未来有升级调整,也便于分配给这个定时任务服务器单独的硬件资源。