java在jdk1.3中推出了定时器类Timer,而后在jdk1.5后由Dou Lea从新开发出了支持多线程的ScheduleThreadPoolExecutor,从后者的表现来看,可以考虑完全替代Timer了。
Timer与ScheduleThreadPoolExecutor对比:
1.
Timer始于jdk1.3,其原理是利用一个TimerTask数组当作队列,将所有定时任务添加到此队列里面去。 然后启动一个线程,当队列为空时,此线程会阻塞,当队列里面有数据时,线程会去除一个TimerTask来判断
是否到时间需要运行此任务,如果运行时间小于或等于当前时间时则开始运行任务。 由于其单线程的本质,所以会带来几个问题(详细代码在后面):
第一,当我们添加到定时器中的任务比较耗时时,由于此定时器是单线程顺序执行定时器任务,所以会影响后续任务的按时执行。
- //问题一示例:
- m_timer.scheduleAtFixedRate(new TaskUseLongTime(), 1000, 5000);
- m_timer.scheduleAtFixedRate(new TaskNormal(), 5000, 3000);
- 运行结果:
- 14:44:29: timer is sleeping 10 seconds
- 14:44:39: Task Normal executed
- 14:44:39: timer is sleeping 10 seconds
- 14:44:49: Task Normal executed
- 14:44:49: Task Normal executed
- 14:44:49: timer is sleeping 10 seconds
- 结果分析:TaskNormal任务无法保证3秒运行一次,其只能等待TaskUseLongTime运行结束后才可以。
第二,Timer中的线程仅仅会捕获InterruptedException异常,所以如果我们自定义的定时任务里面没有捕获可能出现的异常而导致异常抛出后,
- //问题二示例:
- m_timer.schedule(new TaskThrowException(), 1000);
- m_timer.schedule(new TaskNormal(), 2000);
- 运行结果:
- 14:47:37: Throw exception
- Exception in thread "Timer-0" java.lang.RuntimeException
- at timer_test.TimerTest$TaskThrowException.run(TimerTest.java:85)
- at java.util.TimerThread.mainLoop(Timer.java:512)
- at java.util.TimerThread.run(Timer.java:462)
- 结果分析:
- 当前一个任务抛出异常后,后面的TaskNormal任务无法继续运行
会导致我们的Timer线程停止,从而另后续的任务无法执行。
第三,其无法处理多个同时发生的定时任务
- //问题三示例:
- m_timer.scheduleAtFixedRate(new TaskUseLongTime("timer1"), 1000, 15000);
- m_timer.scheduleAtFixedRate(new TaskUseLongTime("timer2"), 1000, 15000);
- 运行结果:
- 14:50:16: timer1 is sleeping 10 seconds
- 14:50:26: timer2 is sleeping 10 seconds
- 14:50:36: timer2 is sleeping 10 seconds
- 结果分析:
- 我的启动时间均是1秒以后,但是timer1和timer2启动的时间明显不一致
代码示例:
- /**
- * @filename TimerTest.java
- * @date 2014-11-18
- */
- package timer_test;
- import java.text.SimpleDateFormat;
- import java.util.Date;
- import java.util.Timer;
- import java.util.TimerTask;
- public class TimerTest
- {
- private final Timer m_timer = new Timer();
- public static void main(String[] args)
- {
- new TimerTest().test();
- }
- public void test()
- {
- //问题一示例:
- m_timer.scheduleAtFixedRate(new TaskUseLongTime(), 1000, 5000);
- m_timer.scheduleAtFixedRate(new TaskNormal(), 5000, 3000);
- //问题二示例:
- // m_timer.schedule(new TaskThrowException(), 1000);
- // m_timer.schedule(new TaskNormal(), 2000);
- //问题三示例:
- // m_timer.scheduleAtFixedRate(new TaskUseLongTime("timer1"), 1000, 5000);
- // m_timer.scheduleAtFixedRate(new TaskUseLongTime("timer2"), 1000, 5000);
- }
- private class TaskUseLongTime extends TimerTask
- {
- private String m_taskName = "timer";
- public TaskUseLongTime(){}
- public TaskUseLongTime(String taskName)
- {
- m_taskName = taskName;
- }
- @Override
- public void run()
- {
- try
- {
- System.out.println(getCurrentTime()+": "+m_taskName+" is sleeping 10 seconds");
- Thread.sleep(10000);
- } catch (InterruptedException e)
- {
- }
- }
- }
- private class TaskNormal extends TimerTask
- {
- @Override
- public void run()
- {
- System.out.println(getCurrentTime()+": Task Normal executed");
- }
- }
- private class TaskThrowException extends TimerTask
- {
- @Override
- public void run()
- {
- System.out.println(getCurrentTime()+": Throw exception");
- throw new RuntimeException();
- }
- }
- private String getCurrentTime()
- {
- return new SimpleDateFormat("HH:mm:ss").format(new Date());
- }
- }
2.ScheduleThreadPoolExecutor
ScheduleThreadPoolExecutor始于jdk1.5,是由Dou Lea先生编写的,其利用ThreadPoolExecutor和DelayQueue巧妙的结合完成了多线程定时器的实现,解决了Timer中由于单线程而导致的上述三个缺陷。
问题一中的问题是因为单线程顺序执行导致后续任务无法按时完成,我们看到多线程可以很容易的解决此问题,同时我们注意到TaskUseLongTime的执行时间为10s(请看后续代码),我们定时任务间隔是5秒,但是从结果中发现我们的任务执行间隔却是10秒,所以我们可以判断ScheduleThreadPoolExecutor是采用每线程每任务的模式工作的。
- //问题一:
- m_timer.scheduleAtFixedRate(new TaskUseLongTime(), 1000, 5000, TimeUnit.MILLISECONDS);
- m_timer.scheduleAtFixedRate(new TaskNormal(), 1000, 5000, TimeUnit.MILLISECONDS);
- 运行结果:
- 14:54:37: Task Normal executed
- 14:54:37: timer is sleeping 10 seconds
- 14:54:42: Task Normal executed
- 14:54:47: Task Normal executed
- 14:54:47: timer is sleeping 10 seconds
- 14:54:52: Task Normal executed
问题二中我们发现当抛出异常的任务执行后不影响其他任务的运行,同时我们发现在运行结果里面没有将我们的异常抛出,这是因为ScheduleThreadPoolExecutor类在执行完定时任务后会返回一个ScheduledFuture运行结果,不论结果是顺利完成还是有异常均会保存在这里。
- //问题二:
- m_timer.scheduleAtFixedRate(new TaskThrowException(), 1000, 5000, TimeUnit.MILLISECONDS);
- m_timer.scheduleAtFixedRate(new TaskNormal(), 1000, 5000, TimeUnit.MILLISECONDS);
- 运行结果:
- 14:58:36: Throw exception
- 14:58:36: Task Normal executed
- 14:58:41: Task Normal executed
- 14:58:46: Task Normal executed
- 14:58:51: Task Normal executed
- 14:58:56: Task Normal executed
问题三由于是多线程所以我们可以保证我们的定时任务可以同时执行
- //问题三:
- m_timer.scheduleAtFixedRate(new TaskUseLongTime("timer1"), 1000, 5000, TimeUnit.MILLISECONDS);
- m_timer.scheduleAtFixedRate(new TaskUseLongTime("timer2"), 1000, 5000, TimeUnit.MILLISECONDS);
- 运行结果:
- 15:01:12: timer1 is sleeping 10 seconds
- 15:01:12: timer2 is sleeping 10 seconds
- 15:01:22: timer2 is sleeping 10 seconds
- 15:01:22: timer1 is sleeping 10 seconds
- 15:01:32: timer1 is sleeping 10 seconds
- 15:01:32: timer2 is sleeping 10 seconds
详细代码:
- /**
- * @filename ScheduleThreadPoolExecutorTest.java
- * @date 2014-11-18
- */
- package timer_test;
- import java.text.SimpleDateFormat;
- import java.util.Date;
- import java.util.concurrent.Callable;
- import java.util.concurrent.ScheduledThreadPoolExecutor;
- import java.util.concurrent.TimeUnit;
- public class ScheduleThreadPoolExecutorTest
- {
- private final ScheduledThreadPoolExecutor m_timer = new ScheduledThreadPoolExecutor(10);
- public static void main(String[] args)
- {
- ScheduleThreadPoolExecutorTest timerTest = new ScheduleThreadPoolExecutorTest();
- timerTest.test();
- try
- {
- Thread.sleep(100000);
- } catch (InterruptedException e)
- {
- }finally
- {
- timerTest.shutdown();
- }
- }
- public void shutdown()
- {
- m_timer.shutdown();
- }
- public void test()
- {
- //问题一:
- // m_timer.scheduleAtFixedRate(new TaskUseLongTime(), 1000, 5000, TimeUnit.MILLISECONDS);
- // m_timer.scheduleAtFixedRate(new TaskNormal(), 1000, 5000, TimeUnit.MILLISECONDS);
- //问题二:
- // m_timer.scheduleAtFixedRate(new TaskThrowException(), 1000, 5000, TimeUnit.MILLISECONDS);
- // m_timer.scheduleAtFixedRate(new TaskNormal(), 1000, 5000, TimeUnit.MILLISECONDS);
- //问题三:
- m_timer.scheduleAtFixedRate(new TaskUseLongTime("timer1"), 1000, 5000, TimeUnit.MILLISECONDS);
- m_timer.scheduleAtFixedRate(new TaskUseLongTime("timer2"), 1000, 5000, TimeUnit.MILLISECONDS);
- }
- private class TaskUseLongTime implements Callable<Integer>, Runnable
- {
- private String m_taskName = "timer";
- private TaskUseLongTime(){}
- private TaskUseLongTime(String taskName)
- {
- m_taskName = taskName;
- }
- public void run()
- {
- try
- {
- System.out.println(getCurrentTime()+": "+m_taskName+" is sleeping 10 seconds");
- Thread.sleep(10000);
- } catch (InterruptedException e)
- {
- }
- }
- public Integer call() throws Exception
- {
- run();
- return 0;
- }
- }
- @SuppressWarnings("unused")
- private class TaskNormal implements Callable<Integer>, Runnable
- {
- public Integer call() throws Exception
- {
- run();
- return 0;
- }
- public void run()
- {
- System.out.println(getCurrentTime()+": Task Normal executed");
- }
- }
- @SuppressWarnings("unused")
- private class TaskThrowException implements Callable<Integer>, Runnable
- {
- public Integer call() throws Exception
- {
- System.out.println(getCurrentTime()+": Throw exception");
- throw new RuntimeException();
- }
- public void run()
- {
- System.out.println(getCurrentTime()+": Throw exception");
- throw new RuntimeException();
- }
- }
- private String getCurrentTime()
- {
- return new SimpleDateFormat("HH:mm:ss").format(new Date());
- }
- }