ScheduleThreadPoolExecutor的工作原理与使用示例
欢迎探讨,如有错误敬请指正
如需转载,请注明出处 http://www.cnblogs.com/nullzx/
1. ScheduleExecutorService接口、ScheduledFuture接口
从图中可以看出ScheduledExecutorService接口继承了ExecutorService接口,同时还添加了有关提交定时任务的四个方法。
public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) //向定时任务线程池提交一个延时Runnable任务(仅执行一次) public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit); //向定时任务线程池提交一个延时的Callable任务(仅执行一次) public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) //向定时任务线程池提交一个固定时间间隔执行的任务 public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit); //向定时任务线程池提交一个固定延时间隔执行的任务
固定时间间隔的任务不论每次任务花费多少时间,下次任务开始执行时间是确定的,当然执行任务的时间不能超过执行周期。
固定延时间隔的任务是指每次执行完任务以后都延时一个固定的时间。由于操作系统调度以及每次任务执行的语句可能不同,所以每次任务执行所花费的时间是不确定的,也就导致了每次任务的执行周期存在一定的波动。
注意:定时或延时任务中所涉及到时间、周期不能保证实时性及准确性,实际运行中会有一定的误差。
从上图可以还可以看出ScheduledThreadPoolExecutor还直接继承了ThreadPoolExecutor。这样做是为了利用ThreadPoolExecutor已实现的方法。
可以向定时任务线程池提交普通任务。对于定时任务线程池而言,普通任务只不过是延时执行时间为0,周期为0的任务。
从上述四个方法中的返回值可以看出,当向线程池提交任务时会返回一个ScheduleFuture接口的对象。通过下图可以看出,ScheduledFuture接口继承了Delayed和Future接口。我们可以通过ScheduleFutured对象的cancel方法可以结束一个定时任务。
在ScheduledThreadPoolExecutor中阻塞队列存储的是ScheduledFutureTask对象,它是任务真正的载体。但是ScheduledFutureTask对象同样可以看做Runnable对象,所以同样可以使用ThreadPoolExecutor中的方法来处理它。
2. ScheduledThreadPoolExecutor的运行原理
在图中,我们将ScheduleFutureTask简称为SFT
在定时任务线程池中存储任务的队列是具有优先性质的阻塞队列。在这个队列中离下次执行时间最近的任务位于队首(关于优先队列的原理请参照本博客后续数据结构的相关内容)。线程每次通过优先队列的take方法获取任务。如果队列为空take方法阻塞,否则take方法从队首获取带执行的任务(即ScheduleFutureTask对象),然后从任务的getDelay方法获取应当延时的时间,再通过带参数的await方法进行延时。当延时时间已到,则开始执行任务(在执行之前还要检测这个任务是否已被取消),执行完毕以后继续判断这是否是一个周期任务。如果不是,调用take方法获取新任务,重复上述操作。如果是计算下一次执行的时间,然后调用队列的add方法将这个任务再次入列。入列完成后,继续调用take方法获取新任务,这样重复的执行下去。
以上就是定时任务线程池工作的最基本原理,实际上take、add、任务的取消等过程比较复杂,图中也没有说明线程在执行await方法时队首元素改变时的情况。如果想继续了解,请参阅本博客“ScheduledThreadPoolExecutor源代码分析”的文章。
3. ScheduleThreadPoolExecutor与Timer相比的优势。
(1)Timer是基于绝对时间的延时执行或周期执行,当系统时间改变,则任务的执行会受到的影响。而ScheduleThreadPoolExecutore中,任务时基于相对时间进行周期或延时操作。
(2)Timer也可以提交多个TimeTask任务,但只有一个线程来执行所有的TimeTask,这样并发性受到影响。而ScheduleThreadPoolExecutore可以设定池中线程的数量。
(3)Timer不会捕获TimerTask的异常,只是简单地停止,这样势必会影响其他TimeTask的执行。而ScheduleThreadPoolExecutore中,如果一个线程因某些原因停止,线程池可以自动创建新的线程来维护池中线程的数量。
4. 使用示例
package javaleanning; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; public class ScheduledThreadPoolExecutorDemo { static class TimerTask implements Runnable{ private String id; public TimerTask(String id){ this.id = id; } @Override public void run(){ System.out.println(id); } } public static void main(String[] args) throws InterruptedException{ ScheduledExecutorService ses = Executors.newScheduledThreadPool(2); ScheduledFuture sfa = ses.scheduleAtFixedRate(new TimerTask("a"), 200, 1000, TimeUnit.MILLISECONDS); ScheduledFuture sfb = ses.scheduleAtFixedRate(new TimerTask("b"), 400, 1000, TimeUnit.MILLISECONDS); ScheduledFuture sfc = ses.scheduleAtFixedRate(new TimerTask("c"), 600, 1000, TimeUnit.MILLISECONDS); ScheduledFuture sfd = ses.scheduleAtFixedRate(new TimerTask("d"), 800, 1000, TimeUnit.MILLISECONDS); Thread.sleep(5000); sfa.cancel(true); Thread.sleep(5000); ses.shutdown(); } }
在这个示例中,定义了一个内部类TimerTask,它实现了Runnable接口。在main方法中创建了一个定时任务线程池,向它提交了四个任务a,b,c,d。四个任务起始的延时时间分别是200ms、400ms、600ms、800ms,执行周期都为1000ms。5秒以后取消了定时任务a的执行。又过了5秒,关闭了线程池。默认情况下关闭线程池会结束池所有的任务。
以下是运行结果
a
b
c
d
a
b
c
d
a
b
c
d
a
b
c
d
a
b
c
d
b
c
d
b
c
d
b
c
d
b
c
d
b
c
d
5. 参考内容
[1] http://janeky.iteye.com/blog/770441