本文来自我一个朋友的个人博客(希望各位以后多多支持):https://www.liupeng.mobi/archives/777
一、Timer类
在java中一个完整的定时任务需要由Timer和TimerTask两个类配合完成。
其中Timer是一种工具,线程用其安排在后台线程中执行的任务,可安排任务执行一次或者定期重复执行;
而TimerTask是由Timer安排执行一次或者重复执行的任务。
Timer中提供了四个构造方法:
schedule(TimerTask task, Date time)——安排在指定的时间执行指定的任务;
schedule(TimerTask task, Date firstTime, long period)——安排指定的任务在指定的时间开始进行重复的固定延迟执行;
schedule(TimerTask task, long delay)——安排在指定延迟后执行指定的任务;
schedule(TimerTask task, long delay, long period)——安排指定的任务在指定的延迟后开始进行重复的固定速率执行。
1、在指定延迟时间执行定时任务
1 public classTestUserTimer {2 public static voidmain(String[] args) {3 System.out.println("任务开始" + newDate());4 timer();5 }6
7 //方法一;设定指定任务task在指定时间time执行schedule(TimeerTask,Date time)
8 private static voidtimer(){9 Timer timer = newTimer();10 timer.schedule(newTimerTask() {11 @Override12 public voidrun() {13 System.out.println("2秒后执行一次该任务" + newDate());14 }15 },2000); //单位毫秒ms,延迟2秒后执行
16 }17 }
执行结果:
任务开始Thu May 31 14:37:33 CST
2秒后执行一次该任务Thu May 31 14:37:35 CST
2. 在指定时间执行定时任务
1 public classTestUserTimer {2 public static voidmain(String[] args) {3 System.out.println("指定的时间为 14:47");4 timer();5 }6
7 //方法二;在指定时间执行任务
8 private static voidtimer(){9 Calendar calendar =Calendar.getInstance();10 calendar.set(Calendar.HOUR_OF_DAY,14);11 calendar.set(Calendar.MINUTE,47);12 calendar.set(Calendar.SECOND,0);13 final Date time =calendar.getTime();14 Timer timer = newTimer();15 timer.schedule(newTimerTask() {16 @Override17 public voidrun() {18 Date date = new Date(this.scheduledExecutionTime());19 System.out.println("14:47时执行一次该任务: " +date);20 }21 },time);22 }23 }
运行结果为:
指定的时间为 14:47
14:47时执行一次该任务: Thu May 31 14:47:00 CST
Date 日期类在JDK1.0中,Date类是唯一的一个代表时间的类,但是由于Date类不便于实现国际化,所以从JDK1.1版本开始,推荐使用Calendar类进行时间和日期处理。这里简单介绍一下Date类的使用。
输出格式: Thu May 31 14:47:00 CST
使用Date类代表指定的时间
Date date = new Date(this.scheduledExecutionTime());
Calendar 日历类注意: 如果延迟执行时间在当前时间之前,则立即执行
从JDK1.1版本开始,在处理日期和时间时,系统推荐使用Calendar类进行实现。
Calender.HOUR 12小时制
Calender.HOUR_OF_DAY 24小时制
注意: 如果延迟执行时间在当前时间之前,则立即执行
3. 在延迟指定时间后以指定的间隔时间循环执行定时任务
1 public classTestUserTimer {2 public static voidmain(String[] args) {3 System.out.println("任务开始");4 timer();5 }6
7 //在延迟指定时间后以指定的间隔时间循环执行定时任务
8 private static voidtimer(){9 Timer timer = newTimer();10 timer.schedule(newTimerTask() {11 @Override12 public voidrun() {13 System.out.println("执行一次该任务: " + newDate());14 }15 },2000,1000);16 }17 }
输出结果:
任务开始
执行一次该任务: Thu May 31 15:35:49 CST
执行一次该任务: Thu May 31 15:35:50 CST
执行一次该任务: Thu May 31 15:35:51 CST
执行一次该任务: Thu May 31 15:35:52 CST
执行一次该任务: Thu May 31 15:35:53 CST
4、Timer类小结
Timer类是一种简单实用的实现定时任务的方法,然而它存在着自身的缺陷:
(1)Timer对调度的支持是基于绝对时间而不是相对时间,因此它对于系统时间的改变非常敏感;
(2)Timer线程是不会捕获异常的,如果TimerTask抛出未检查的异常则会导致Timer线程终止,同时Timer也不会重新恢复线程的执行,它会错误的认为整个Timer线程都会取消,已经被安排但尚未执行的TimerTask也不会再执行了,新的任务也不能被调度。因此,如果TimerTask抛出未检查的异常,Timer将会产生无法预料的行为。
二、ScheduledExecutorService
ScheduledExecutorService是基于相对时间;Timer内部是单一线程,
而ScheduledThreadPoolExecutor内部是个线程池,可以支持多个任务并发执行。
ScheduledExecutor的设计思想是每一个被调度的任务都会由线程池中一个线程去执行,因此任务是并发的,相互之间不会受到干扰;只有当任务的时间到来时,ScheduledExecutor才会真正启动一个线程。
1、Timer的第一个缺陷
1 public classTestUserTimer {2 privateTimer timer;3 private longstart;4
5 publicTestUserTimer() {6 this.timer = newTimer();7 start =System.currentTimeMillis();8 }9
10 /**
11 * 延迟一秒执行调度任务,输出后睡眠4 秒12 */
13 public voidtimerOne(){14 timer.schedule(newTimerTask() {15 @Override16 public voidrun() {17 System.out.println("任务1开始执行: " + (System.currentTimeMillis()- start) +"毫秒");18 try{19 Thread.sleep(4000);20 } catch(InterruptedException e) {21 e.printStackTrace();22 }23 }24 },1000);25 }26
27 public voidtimerTwo(){28 timer.schedule(newTimerTask() {29 @Override30 public voidrun() {31 System.out.println("任务2开始执行: " + (System.currentTimeMillis()-start) + "毫秒");32 }33 },3000);34 }35 public static voidmain(String[] args) {36 TestUserTimer testUserTimer = newTestUserTimer();37 System.out.println("任务开始: " +testUserTimer.start);38 testUserTimer.timerOne();39 testUserTimer.timerTwo();40 }41 }
运行结果:
任务开始: 1527753568049
任务1开始执行: 1007毫秒
任务2开始执行: 5007毫秒
按照设想,任务1与开始时间间隔为1秒,而任务2与开始时间的时间间隔为3秒。然而,由于Timer在执行定时任务时只会创建一个工作线程,当工作线程因为某种原因而导致线程任务执行时间过长,超过了两个任务的间隔时间,则会出现以上情况。
上述代码说明Timer类是单线程的.
使用ScheduledExecutorService优化:
1 public classTestScheduledExecutorTimer {2
3 privateScheduledExecutorService schedule;4 private longstart;5
6 publicTestScheduledExecutorTimer() {7 this.schedule = Executors.newScheduledThreadPool(2); //执行器创建预期的线程池
8 start =System.currentTimeMillis();9 }10
11
12 public voidtimerOne(){13 schedule.schedule(newRunnable() {14 @Override15 public voidrun() {16 System.out.println("任务开始执行,与开始时间的时间间隔为: " +(System.currentTimeMillis() - start) + "毫秒");17 try{18 Thread.sleep(4000);19 } catch(InterruptedException e) {20 e.printStackTrace();21 }22 }23 },1000, TimeUnit.MILLISECONDS);24 }25
26 public voidtimerTwo(){27 schedule.schedule(newRunnable() {28 @Override29 public voidrun() {30 System.out.println("任务2开始执行: " + (System.currentTimeMillis()-start) + "毫秒");31 }32 },2000,TimeUnit.MILLISECONDS);33 }34
35 public static voidmain(String[] args) {36 TestScheduledExecutorTimer testScheduledExecutorTimer = newTestScheduledExecutorTimer();37 System.out.println("任务开始: " +testScheduledExecutorTimer.start);38 testScheduledExecutorTimer.timerOne();39 testScheduledExecutorTimer.timerTwo();40 }41 }
输出结果:
任务开始: 1527760168681
任务开始执行,与开始时间的时间间隔为: 1004毫秒
任务2开始执行: 毫秒
上述代码说明ScheduledExecutorService是多线程并行的
2.Timer的第二个缺陷
1 public classTestTimerDemo {2
3 privateTimer timer;4 private longstart;5
6 publicTestTimerDemo() {7 this.timer = newTimer();8 start =System.currentTimeMillis();9 }10
11
12 public voidtimerOne(){13 timer.schedule(newTimerTask() {14 @Override15 public voidrun() {16 throw newRuntimeException();17 }18 },1000);19 }20
21 public voidtimerTwo(){22 timer.schedule(newTimerTask() {23 @Override24 public voidrun() {25 System.out.println("任务2开始执行: " + newDate());26 }27 },3000);28 }29
30 public static voidmain(String[] args) {31 TestTimerDemo timerDemo = newTestTimerDemo();32 System.out.println("任务开始: " +timerDemo.start);33 timerDemo.timerOne();34 timerDemo.timerTwo();35 }36 }
输出结果:
任务开始: 1527761009205
Exception in thread “Timer-0” java.lang.RuntimeException
at com.sojson.test.TestTimerDemo$1.run(TestTimerDemo.java:45)
at java.util.TimerThread.mainLoop(Timer.java:555)
at java.util.TimerThread.run(Timer.java:505)
上述代码表名
timerOne抛出异常,而timerTwo并没有执行。
使用ScheduledExecutorService优化:
1 public classTestScheduledExecutorTimer {2
3 privateScheduledExecutorService schedule;4 private longstart;5
6 publicTestScheduledExecutorTimer() {7 this.schedule = Executors.newScheduledThreadPool(2); //执行器创建预期的线程池
8 start =System.currentTimeMillis();9 }10
11 /**
12 * 延迟一秒执行调度任务,输出后睡眠4 秒13 */
14 public voidtimerOne(){15 schedule.schedule(newRunnable() {16 @Override17 public voidrun() {18 throw newRuntimeException();19 }20 },1000, TimeUnit.MILLISECONDS);21 }22
23 public voidtimerTwo(){24 schedule.schedule(newRunnable() {25 @Override26 public voidrun() {27 System.out.println("任务2开始执行: " + (System.currentTimeMillis()-start) + "毫秒");28 }29 },2000,TimeUnit.MILLISECONDS);30 }31
32 public static voidmain(String[] args) {33 TestScheduledExecutorTimer testScheduledExecutorTimer = newTestScheduledExecutorTimer();34 System.out.println("任务开始: " +testScheduledExecutorTimer.start);35 testScheduledExecutorTimer.timerOne();36 testScheduledExecutorTimer.timerTwo();37 }38 }
输出结果:
任务开始: 1527761726364
任务2开始执行: 2025毫秒
上述代码说明:ScheduledExcetorService是多线程的,线程之间互不影响.
3.ScheduledExecutorService小结
可以看到针对Timer类存在的两个缺陷,ScheduledExecutorService可以很好地解决,其思路主要在于每一个被调度的任务都是由线程池中的一个线程去执行,任务之间是并发的,不会互相干扰。