缘起
我们的代码里用到了Timer,差不多是这样:
Timer timer = new Timer("xxx");
timer.schedule(task, 0, 20);
每20ms执行一次task,这里的task并不是耗时操作,基本在1ms左右完成,但是在某些6.x设备上的调度结果显示,差不多是60ms才执行一次,和指定的20ms相差很远啊!!!这么神奇的现象于我来说怎么会放过呢,于是乎点进去研究了发源码。下面主要说下几个关键的结论,供参考。
结论1
每个Timer背后对应一个线程,n多个TimerTask都会在这个timer实例对应的线程中执行。其中只要有某一个task的run方法抛了异常,那么会导致整个timer的thread提前结束,后面所有的任务都不会被执行;坑!!!
结论2
Timer对调度的支持是基于绝对时间,即实现里用到了System.currentTimeMillis()
,而不是相对时间的,因此任务对系统时钟的改变是敏感的,而ScheduledThreadPoolExecutor只支持相对时间;
结论3
重复执行的schedule方法是相对上次task执行完的时间,比如period是10s,现在时间是9:00:10,假设上一个task执行完是50s,那么得到9:01:00的时候,第2个task才会被调度;可以看出在fixed-delay模式下,下一个task什么时候被调度取决于上一个task什么时候执行完,这样就造成了时效准确性问题;
* In fixed-delay execution, each execution is scheduled relative to
* the actual execution time of the previous execution. If an execution
* is delayed for any reason (such as garbage collection or other
* background activity), subsequent executions will be delayed as well.
结论4
重复执行的scheduleAtFixedRate方式是相对上次task开始执行的时间(被调度的时间),所以各个task开始执行的时间都是固定的,但因为所有task需要在同一个线程中执行,所以会存在比如某次task执行的时间比较久,导致当前时间超过了接下来2、3个task的调度时间,就会出现连续快速的调度现象,并不是你想象的间隔period,因为只有这样才能确保整体平均下来确实是按照fixed-rated调度的;
结论5
在Java5或更高的版本中,几乎没有任何理由再使用Timer了,请使用ScheduledThreadPoolExecutor代替,参考《Java并发编程实践6.2.5节》。