Java實現定時器 java實現定時器的四種方式



文章插圖
Java實現定時器 java實現定時器的四種方式

文章插圖
Java在1.3版本引入了Timer工具類,它是一個古老的定時器,搭配TimerTask和TaskQueue一起使用 。從Java5開始在并發包中引入了另一個定時器ScheduledThreadPoolExecutor,它對Timer做了很多改進并提供了更多的工具,可以認為是對Timer的取代 。
那為什么還要介紹Timer工具類呢?通過了解Timer的功能和它背后的原理,有助于我們更好的對比了解ScheduledThreadPoolExecutor,同時ScheduledThreadPoolExecutor的一些改進思想在我們平時的編碼工作中也可以借鑒 。
主要成員變量
Timer中用到的主要是兩個成員變量:
TaskQueue:一個按照時間優先排序的隊列,這里的時間是每個定時任務下一次執行的毫秒數(相對于1970年1月1日而言)TimerThread:對TaskQueue里面的定時任務進行編排和觸發執行,它是一個內部無限循環的線程 。
//根據時間進行優先排序的隊列private final TaskQueue queue = new TaskQueue();//消費線程,對queue中的定時任務進行編排和執行private final TimerThread thread = new TimerThread(queue);//構造函數public Timer(String name) {thread.setName(name);thread.start();}定時功能
Timer提供了三種定時模式:
一次性任務按照固定的延遲執行(fixed delay)按照固定的周期執行(fixed rate)
第一種比較好理解,即任務只執行一次;針對第一種,Timer提供了以下兩個方法:
//在當前時間往后delay個毫秒開始執行public void schedule(TimerTask task, long delay) {...}//在指定的time時間點執行public void schedule(TimerTask task, Date time) {...}第二種Fixed Delay模式也提供了以下兩個方法
//從當前時間開始delay個毫秒數開始定期執行,周期是period個毫秒數public void schedule(TimerTask task, long delay, long period) {...}////從指定的firstTime開始定期執行,往后每次執行的周期是period個毫秒數public void schedule(TimerTask task, Date firstTime, long period){...}它的工作方式是:
第一次執行的時間將按照指定的時間點執行(如果此時TimerThread不在執行其他任務),如有其他任務在執行,那就需要等到其他任務執行完成才能執行 。
從第二次開始,每次任務的執行時間是上一次任務開始執行的時間加上指定的period毫秒數 。
如何理解呢,我們還是看代碼
public static void main(String[] args) {TimerTask task1 = new DemoTimerTask("Task1");TimerTask task2 = new DemoTimerTask("Task2");Timer timer = new Timer();timer.schedule(task1, 1000, 5000);timer.schedule(task2, 1000, 5000);}static class DemoTimerTask extends TimerTask {private String taskName;private DateFormat df = new SimpleDateFormat("HH:mm:ss---");public DemoTimerTask(String taskName) {this.taskName = taskName;}@Overridepublic void run() {System.out.println(df.format(new Date()) + taskName + " is working.");try {Thread.sleep(2000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}System.out.println(df.format(new Date()) + taskName + " finished work.");}}task1和task2是幾乎同時執行的兩個任務,而且執行時長都是2秒鐘,如果此時我們把第六行注掉不執行,我們將得到如下結果(和第三種Fixed Rate模式結果相同):
13:42:58---Task1 is working.13:43:00---Task1 finished work.13:43:03---Task1 is working.13:43:05---Task1 finished work.13:43:08---Task1 is working.13:43:10---Task1 finished work.如果打開第六行,我們再看下兩個任務的執行情況 。我們是期望兩個任務能夠同時執行,但是Task2是在Task1執行完成后才開始執行(原因是TimerThread是單線程的,每個定時任務的執行也在該線程內完成,當多個任務同時需要執行時,只能是阻塞了),從而導致Task2第二次執行的時間是它上一次執行的時間(13:43:57)加上5秒鐘(13:44:02) 。
13:43:55---Task1 is working.13:43:57---Task1 finished work.13:43:57---Task2 is working.13:43:59---Task2 finished work.13:44:00---Task1 is working.13:44:02---Task1 finished work.13:44:02---Task2 is working.13:44:04---Task2 finished work.那如果此時還有個Task3也是同樣的時間點和間隔執行會怎么樣呢?
結論是:也將依次排隊,執行的時間依賴兩個因素:
1.上次執行的時間
2.期望執行的時間點上有沒有其他任務在執行,有則只能排隊了
【Java實現定時器 java實現定時器的四種方式】我們接下來看下第三種Fixed Rate模式,我們將上面的代碼稍作修改:
public static void main(String[] args) {TimerTask task1 = new DemoTimerTask("Task1");TimerTask task2 = new DemoTimerTask("Task2");Timer timer = new Timer();timer.scheduleAtFixedRate(task1, 1000, 5000);timer.scheduleAtFixedRate(task2, 1000, 5000);}static class DemoTimerTask extends TimerTask {private String taskName;private DateFormat df = new SimpleDateFormat("HH:mm:ss---");public DemoTimerTask(String taskName) {this.taskName = taskName;}@Overridepublic void run() {System.out.println(df.format(new Date()) + taskName + " is working.");try {Thread.sleep(2000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}System.out.println(df.format(new Date()) + taskName + " finished work.");}}Task1和Task2還是在相同的時間點,按照相同的周期定時執行任務,我們期望Task1能夠每5秒定時執行任務,期望的時間點是:14:21:47-14:21:52-14:21:57-14:22:02-14:22:07,實際上它能夠交替著定期執行,原因是Task2也會定期執行,并且對TaskQueue的鎖他們是交替著拿的(這個在下面分析TimerThread源碼的時候會講到)
14:21:47---Task1 is working.14:21:49---Task1 finished work.14:21:49---Task2 is working.14:21:51---Task2 finished work.14:21:52---Task2 is working.14:21:54---Task2 finished work.14:21:54---Task1 is working.14:21:56---Task1 finished work.14:21:57---Task1 is working.14:21:59---Task1 finished work.14:21:59---Task2 is working.14:22:01---Task2 finished work.TimerThread
上面我們主要講了Timer的一些主要源碼及定時模式,下面我們來分析下支撐Timer的定時任務線程TimerThread 。
TimerThread大概流程圖如下:
TimerThread流程
源碼解釋如下:
private void mainLoop() {while (true) {try {TimerTask task;boolean taskFired;synchronized(queue) {// 如果queue里面沒有要執行的任務,則掛起TimerThread線程while (queue.isEmpty() && newTasksMayBeScheduled)queue.wait();// 如果TimerThread被激活,queue里面還是沒有任務,則介紹該線程的無限循環,不再接受新任務if (queue.isEmpty())break;long currentTime, executionTime;// 獲取queue隊列里面下一個要執行的任務(根據時間排序,也就是接下來最近要執行的任務)task = queue.getMin();synchronized(task.lock) {if (task.state == TimerTask.CANCELLED) {queue.removeMin();continue;// No action required, poll queue again}currentTime = System.currentTimeMillis();executionTime = task.nextExecutionTime;// taskFired表示是否需要立刻執行線程,當task的下次執行時間到達當前時間點時為trueif (taskFired = (executionTime<=currentTime)) {//task.period==0表示這個任務只需要執行一次,這里就從queue里面刪掉了if (task.period == 0) {queue.removeMin();task.state = TimerTask.EXECUTED;} else { // Repeating task, reschedule//針對task.period不等于0的任務,則計算它的下次執行時間點//task.period<0表示是fixed delay模式的任務//task.period>0表示是fixed rate模式的任務queue.rescheduleMin(task.period<0 ? currentTime- task.period: executionTime + task.period);}}}// 如果任務的下次執行時間還沒有到達,則掛起TimerThread線程executionTime - currentTime毫秒數,到達執行時間點再自動激活if (!taskFired)queue.wait(executionTime - currentTime);}// 如果任務的下次執行時間到了,則執行任務// 注意:這里任務執行沒有另起線程,還是在TimerThread線程執行的,所以當有任務在同時執行時會出現阻塞if (taskFired)// 這里沒有try catch異常,當TimerTask拋出異常會導致整個TimerThread跳出循環,從而導致Timer失效task.run();} catch(InterruptedException e) {}}}結論
通過上面的分析,我們可以得出以下結論:
Timer支持三種模式的定時任務(一次性任務,Fixed Delay模式,Fixed Rate模式)Timer中的TimerThread是單線程模式,因此導致所有定時任務不能同時執行,可能會出現延遲TimerThread中并沒有處理好任務的異常,因此每個TimerTask的實現必須自己try catch防止異常拋出,導致Timer整體失效Demo代碼位置