Timer线程的缺点(这个就重要了)
1.Timer线程不会捕获异常,所以TimerTask抛出的未检查的异常会终止timer线程。如果Timer线程中存在多个计划任务,其中一个计划任务抛出未检查的异常,则会引起整个Timer线程结束,从而导致其他计划任务无法得到继续执行。
2.Timer线程时基于绝对时间(如:2014/02/14 16:06:00),因此计划任务对系统的时间的改变是敏感的。(举个例子,假如你希望任务1每个10秒执行一次,某个时刻,你将系统时间提前了6秒,那么任务1就会在4秒后执行,而不是10秒后)
3.Timer是单线程,如果某个任务很耗时,可能会影响其他计划任务的执行。
4.Timer执行程序是有可能延迟1、2毫秒,如果是1秒执行一次的任务,1分钟有可能延迟60毫秒,一小时延迟3600毫秒,相当于3秒(如果你的任务对时间敏感,这将会有影响) ScheduledThreadPoolExecutor的时间会更加的精确
ScheduledThreadPoolExecutor解决了上述所有问题~
ScheduledThreadPoolExecutor(JDK全新定时器调度)
ScheduledThreadPoolExecutor是JDK1.5以后推出的类,用于实现定时、重复执行的功能,官方文档解释要优于Timer。
构造方法:
ScheduledThreadPoolExecutor(int corePoolSize) //使用给定核心池大小创建一个新定定时线程池 ScheduledThreadPoolExecutor(int corePoolSize, ThreadFactorythreadFactory) //使用给定的初始参数创建一个新对象,可提供线程创建工厂
需要手动传入线程工厂的,可以这么弄:
private final static ScheduledThreadPoolExecutor schedual = new ScheduledThreadPoolExecutor(1, new ThreadFactory() { private AtomicInteger atoInteger = new AtomicInteger(0); @Override public Thread newThread(Runnable r) { Thread t = new Thread(r); t.setName("xxx-Thread " + atoInteger.getAndIncrement()); return t; } });
相关调度方法:
ScheduledThreadPoolExecutor还提供了非常灵活的API,用于执行任务。其任务的执行策略主要分为两大类:
①在一定延迟之后只执行一次某个任务;
②在一定延迟之后周期性的执行某个任务;
如下是其主要API:
// 执行一次 public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit); public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit); // 周期性执行 public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit); public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit);
第一个和第二个方法属于第一类,即在delay指定的延迟之后执行第一个参数所指定的任务,区别在于,第二个方法执行之后会有返回值,而第一个方法执行之后是没有返回值的。
第三个和第四个方法则属于第二类,即在第二个参数(initialDelay)指定的时间之后开始周期性的执行任务,执行周期间隔为第三个参数指定的时间。
但是这两个方法的区别在于:第三个方法执行任务的间隔是固定的,无论上一个任务是否执行完成(也就是前面的任务执行慢不会影响我后面的执行)。而第四个方法的执行时间间隔是不固定的,其会在周期任务的上一个任务执行完成之后才开始计时,并在指定时间间隔之后才开始执行任务。
public class ScheduledThreadPoolExecutorTest { private ScheduledThreadPoolExecutor executor; private Runnable task; @Before public void before() { executor = initExecutor(); task = initTask(); } private ScheduledThreadPoolExecutor initExecutor() { return new ScheduledThreadPoolExecutor(2);; } private Runnable initTask() { long start = System.currentTimeMillis(); return () -> { print("start task: " + getPeriod(start, System.currentTimeMillis())); sleep(SECONDS, 10); print("end task: " + getPeriod(start, System.currentTimeMillis())); }; } @Test public void testFixedTask() { print("start main thread"); executor.scheduleAtFixedRate(task, 15, 30, SECONDS); sleep(SECONDS, 120); print("end main thread"); } @Test public void testDelayedTask() { print("start main thread"); executor.scheduleWithFixedDelay(task, 15, 30, SECONDS); sleep(SECONDS, 120); print("end main thread"); } private void sleep(TimeUnit unit, long time) { try { unit.sleep(time); } catch (InterruptedException e) { e.printStackTrace(); } } private int getPeriod(long start, long end) { return (int)(end - start) / 1000; } private void print(String msg) { System.out.println(msg); } } 第一个输出: start main thread start task: 15 end task: 25 start task: 45 end task: 55 start task: 75 end task: 85 start task: 105 end task: 115 end main thread 第二个输出: start main thread start task: 15 end task: 25 start task: 55 end task: 65 start task: 95 end task: 105 end main thread
从结果,现在重点说说这两者的区别:
scheduleAtFixedRate
是以上一个任务开始的时间计时,period时间过去后,检测上一个任务是否执行完毕,如果上一个任务执行完毕,则当前任务立即执行,如果上一个任务没有执行完毕,则需要等上一个任务执行完毕后立即执行。
执行周期是 initialDelay 、initialDelay+period 、initialDelay + 2 * period} 、 … 如果延迟任务的执行时间大于了 period,比如为 5s,则后面的执行会等待5s才回去执行
scheduleWithFixedDelay
是以上一个任务结束时开始计时,period时间过去后,立即执行, 由上面的运行结果可以看出,第一个任务开始和第二个任务开始的间隔时间是 第一个任务的运行时间+period(永远是这么多)
注意: 通过ScheduledExecutorService执行的周期任务,如果任务执行过程中抛出了异常,那么过ScheduledExecutorService就会停止执行任务,且也不会再周期地执行该任务了。所以你如果想保住任务都一直被周期执行,那么catch一切可能的异常。
关于ScheduledThreadPoolExecutor的使用有三点需要说明
1.ScheduledThreadPoolExecutor继承自ThreadPoolExecutor(ThreadPoolExecutor详解),因而也有继承而来的execute()和submit()方法,但是ScheduledThreadPoolExecutor重写了这两个方法,重写的方式是直接创建两个立即执行并且只执行一次的任务;
2.ScheduledThreadPoolExecutor使用ScheduledFutureTask封装每个需要执行的任务,而任务都是放入DelayedWorkQueue队列中的,该队列是一个使用数组实现的优先队列,在调用ScheduledFutureTask::cancel()方法时,其会根据removeOnCancel变量的设置来确认是否需要将当前任务真正的从队列中移除,而不只是标识其为已删除状态;
3.ScheduledThreadPoolExecutor提供了一个钩子方法decorateTask(Runnable, RunnableScheduledFuture)用于对执行的任务进行装饰,该方法第一个参数是调用方传入的任务实例,第二个参数则是使用ScheduledFutureTask对用户传入任务实例进行封装之后的实例。这里需要注意的是,在ScheduledFutureTask对象中有一个heapIndex变量,该变量用于记录当前实例处于队列数组中的下标位置,该变量可以将诸如contains(),remove()等方法的时间复杂度从O(N)降低到O(logN),因而效率提升是比较高的,但是如果这里用户重写decorateTask()方法封装了队列中的任务实例,那么heapIndex的优化就不存在了,因而这里强烈建议是尽量不要重写该方法,或者重写时也还是复用ScheduledFutureTask类。