好好的Timer居然有坑?

在做定時任務時,可能會使用到Timer+TimerTask類,但是這兩個小小的類,卻有大坑。

先來複現一下問題,如下,可能預期的是第一個PrintTask從1一直往後打印,直到爲5時拋出異常,第二個PrintTask從100往後不間斷打印。

public class Main12 {
    public static void main(String[] args) {
        Timer timer =new Timer();
        timer.schedule(new PrintTask(1),0,1000);
        timer.schedule(new PrintTask(100),0,1000);
    }
    static class PrintTask extends java.util.TimerTask{
        private  int mStart;

        public PrintTask(int start) {
            mStart = start;
        }

        @Override
        public void run() {
            if (mStart==5){
               throw  new RuntimeException();
            }
            System.out.println("mStart=="+mStart++);
        }
    }
}
mStart==1
mStart==100
mStart==101
mStart==2
mStart==3
mStart==102
mStart==4
mStart==103
Exception in thread "Timer-0" java.lang.RuntimeException
	at main.Main12$PrintTask.run(Main12.java:21)
	at java.util.TimerThread.mainLoop(Timer.java:555)
	at java.util.TimerThread.run(Timer.java:505)

Process finished with exit code 0

但是結果卻是,在第一個PrintTask到5拋出異常後,第二個PrintTask卻奇蹟般的終止了。
這裏是不是說明某個TimerTask中的run方法向外拋出異常後,其他TimerTask會自動終止呢?

那就得看Timer的實現原理了。

Timer實現原理

在這裏插入圖片描述
Timer中維護這一個TaskQueue和一個TimerThread,TaskQueue是一個由平衡二叉樹實現的優先級隊列,在調用Timer的schedule方法時,也就是把TimerTask放入TaskQueue中。TimerThread則是具體執行任務的線程,它從TaskQueue中獲取優先級最高的任務去執行,並且只有當前任務執行完後纔會從隊列中獲取下一個任務,不管隊列裏是否有任務已經到了設置的delay時間。這個模型也就是多生產者—單消費者(只要這個消費者線程掛了,其他任務就沒辦法執行)。

所以,重中之重在於TimerThread,只需要搞明白TimerThread就可以了。

在TimerThread的mainLoop方法中開始獲取任務並執行,當任務拋出InterruptedException以外的異常時,唯一的消費者線程就會因異常而終止,隊列裏的其他任務就會被清除。

public void run() {
    try {
        mainLoop();
    } finally {
        synchronized(queue) {
            newTasksMayBeScheduled = false;
            queue.clear();  
        }
    }
}
  private void mainLoop() {
      while (true) {
          try {
              TimerTask task;
              boolean taskFired;
              //從隊列裏面獲取任務時加鎖。
              synchronized(queue) {
              	......
              }
              if (taskFired)  
                  task.run(); //執行任務
          } catch(InterruptedException e) {
          }
      }

所以在TimerTask的run方法中,最好使用try-catch結構捕捉異常,不要向上拋。

還可以使用ScheduledThreadPoolExecutor,ScheduledThreadPoolExecutor中的一個任務拋出異常,其他任務也不受影響。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章