自JDK1.5开始,JDK提供了ScheduledThreadPoolExecutor类来支持周期性任务的调度。在这之前的实现需要依靠Timer和TimerTask或者其它第三方工具来完成。但Timer有不少的缺陷:
Timer是单线程模式;
如果在执行任务期间某个TimerTask耗时较久,那么就会影响其它任务的调度;
Timer的任务调度是基于绝对时间的,对系统时间敏感;
Timer不会捕获执行TimerTask时所抛出的异常,由于Timer是单线程,所以一旦出现异常,则线程就会终止,其他任务也得不到执行。
ScheduledThreadPoolExecutor继承ThreadPoolExecutor来重用线程池的功能,它的实现方式如下:
将任务封装成ScheduledFutureTask对象,ScheduledFutureTask基于相对时间,不受系统时间的改变所影响;
ScheduledFutureTask实现了java.lang.Comparable
接口和java.util.concurrent.Delayed
接口,所以有两个重要的方法:compareTo和getDelay。compareTo方法用于比较任务之间的优先级关系,如果距离下次执行的时间间隔较短,则优先级高;getDelay方法用于返回距离下次任务执行时间的时间间隔;
ScheduledThreadPoolExecutor定义了一个DelayedWorkQueue,它是一个有序队列,会通过每个任务按照距离下次执行时间间隔的大小来排序;
ScheduledFutureTask继承自FutureTask,可以通过返回Future对象来获取执行的结果。
通过如上的介绍,可以对比一下Timer和ScheduledThreadPoolExecutor:
Timer | ScheduledThreadPoolExecutor |
---|---|
单线程 | 多线程 |
单个任务执行时间影响其他任务调度 | 多线程,不会影响 |
基于绝对时间 | 基于相对时间 |
一旦执行任务出现异常不会捕获,其他任务得不到执行 | 多线程,单个任务的执行不会影响其他线程 |
所以,在JDK1.5之后,应该没什么理由继续使用Timer进行任务调度了。
ScheduledThreadPoolExecutor实现分析:
该线程池与普通的ThreadPoolExecutor类似 就是阻塞队列用了DelayedWorkQueue,这种队列就是利用了最小堆的存储结构对任务进行存放。schedule方法类似submit方法,将任务提交,同时设置delay时间和单位还有周期等,当然这个类也提供submit和execute。放在最小堆顶端的节点是delay最小的任务,也就是每当worker从队列中取出第一个任务都是delay最小的任务,然后查看他的delay值,通过condition.awaitNanos方法阻塞线程到delay时间,等待delay时间后worker才开始执行那个任务。
Timer与ScheduledThreadPoolExecutor
原文地址:http://blog.51cto.com/2839840/2044718