2013-07-23 49 views
2

我一直在撓頭,試圖找出Java定時器的掛起問題。我想知道這裏有人可以幫忙。高度讚賞任何幫助診斷問題。Java定時器掛起問題

我有一個簡單的程序,有三個TimerTask類(A,B和Stopper)。 A和B分別每400ms和500ms反覆運行。定時器任務計劃在2秒內運行以關閉所有內容。計時器按預期啓動,並且run()方法的任務按預期執行。但是,一旦停止任務執行,我希望程序終止,但它只是在打印「所有任務和定時器被取消,退出」後才掛起。我已經使用jstack來診斷問題嘗試,但並沒有什麼明顯的指示什麼,如果有什麼需要發佈/停止/取消等

這裏是我的代碼:

package com.example.experiments; 

import java.util.Date; 

/** 
* A test timer class to check behavior of exit/hang issues 
*/ 
public class TimerTest { 

    TimerTest(){ 
    } 

    class TaskA extends java.util.TimerTask { 

     TaskA(){ 
     } 
     public void run() { 
      System.err.println("A.run() called."); 

      if (!running){ 
       System.err.println("A: calling this.cancel()."); 
       this.cancel(); 
       return; 
      } 

     } 
     public boolean cancel(){ 
      System.err.println("Canceling TaskA"); 
      return super.cancel(); 
     } 
    } 

    class TaskB extends java.util.TimerTask { 

     TaskB(){ 
     } 

     public void run(){ 
      System.err.println("B.run() called."); 

      if (!running){ 
       System.err.println("B: calling this.cancel()."); 
       this.cancel(); 
       return; 
      } 

     } 
     public boolean cancel(){ 
      System.err.println("Canceling TaskB"); 
      return super.cancel(); 
     } 
    } 


    private void start(){ 
     this.running = true; // Flag to indicate if the server loop should continue running or not 

     final java.util.Timer timerA = new java.util.Timer(); 
     final TaskA taskA = new TaskA(); 
     timerA.schedule(taskA, 0, 400); 

     final java.util.Timer timerB = new java.util.Timer(); 
     final TaskB taskB = new TaskB(); 
     timerB.schedule(taskB, 0, 500); 

     class StopperTask extends java.util.TimerTask { 
      private java.util.Timer myTimer; 

      StopperTask(java.util.Timer timer){ 
       myTimer = timer; 
      } 

      public void run(){ 
       taskA.cancel(); 
       taskB.cancel(); 
       timerA.cancel(); 
       timerB.cancel(); 

       this.cancel(); 
       myTimer.cancel(); 
       System.err.println("Stopper task completed"); 
      } 
     } 
     final java.util.Timer stopperTimer = new java.util.Timer(); 
     final StopperTask stopperTask = new StopperTask(stopperTimer); 
     stopperTimer.schedule(stopperTask, 2*1000); 


     /** Register witjh JVM to be notified on when the JVM is about to exit */ 
     java.lang.Runtime.getRuntime().addShutdownHook(new Thread() { 
      @Override 
      public void run() { 
       System.err.println("shutting down..."); 
       running = false; 

       taskA.cancel(); 
       taskB.cancel(); 
       timerA.cancel(); 
       timerB.cancel(); 

       stopperTask.cancel(); 
       stopperTimer.cancel(); 

       System.err.println("All tasks and timers canceled, exiting"); 
       System.exit(0); 
      } 
     });  

    } 

    public static void main(String[] args) { 
     new TimerTest().start(); 
    } 

    private boolean running = false; 
} 
+0

只是一個好奇的評論,你爲什麼不導入所有的類,而是用全名引用它們?你顯然有進口的能力。 – skiwi

+0

「一切都按預期進行,但程序只是在Stopper任務執行時掛起」,這句話令人困惑。阻止者任務總是在某個時間點發出(或)? – kosa

+0

添加條件以測試進程是否正在運行,然後取消它。查看它是否掛起。 – Phani

回答

0

而不是System.exit(0)執行返回。另外,你應該標記你的運行變量volatile。

+0

不應該System.exit(0)關閉JVM嗎?你可以添加更多的澄清? – kosa

+0

@Karthik - 謝謝,那有效!但我不明白爲什麼。我可以要求你解釋爲什麼,或指向一個鏈接? – PKK

+0

@PKK看到我的答案澄清 – linski

-1

「StopperTask.cancel()」hmmmmmmmmmmmmmm。 看看序列,它的調用之前退出不再存在!

+0

對不起,我不明白。你的意思是什麼退出不存在? – PKK

5

由於Karthik回答,刪除System.exit(0)和程序不會掛起。我也同意他關於volatile關鍵字的評論。

當關閉鉤子正在運行時,JVM已處於其關閉序列中,並被「靜態」監視器保護。當時調用System.exit(0)方法將有效地將JVM置於deadlock state中。

考慮下面的代碼示例:

public static void main(String[] args) { 
    System.out.println(Thread.currentThread().getName()); 
    java.lang.Runtime.getRuntime().addShutdownHook(new Thread() { 
     @Override 
     public void run() { 
      System.out.println(Thread.currentThread().getName()); 
      System.exit(0);     
     } 
    }); 
} 

它也將掛起 - 紅色方塊按鈕意味着該程序仍在運行,正如你可以在控制檯選項卡中看到它打印出線程的名稱運行該main方法(main)和線程的名字運行的關閉掛鉤(Thread-0):

Eclipse IDE screenshot

當你調用System.exit方法,這將反過來被調用的方法是Shutdown.exit方法(I省略所有的不相干源):

static void exit(int status) { 

    ... 

    synchronized (Shutdown.class) { // "static" monitor mentioned in the first part of the post   
     sequence(); 
     halt(status); 
    } 
} 

sequence方法運行所有的鉤子和終結,而halt方法調用本機halt0方法在這一點上,JVM最終會退出,我想。

因此,這是發生了什麼:

  • main方法在main線程運行,它打印的線程名稱並註冊關閉掛鉤
  • ,因爲在它沒有其他的代碼,該main線程死亡
  • DestroyJavaVM線程開始執行JVM
  • DestroyJavaVM螺紋的關閉進入所述同步塊在Shutdown.exit方法並取得Shutdown.class顯示器
  • sequence方法運行已註冊的關閉掛鉤
  • Thread-0啓動線程來運行我們在main方法註冊的關閉掛鉤
  • Thread-0線程打印其名稱,並啓動另一個JVM關閉經由System.exit方法,這反過來又試圖獲取Shutdown.class監視器但不能,因爲它已經獲得的

綜上所述:

  • Thread-0線程DestroyJavaVM線程等待完成
  • Thread-0線程等待DestroyJavaVM線程完成

這是僵局的定義。

注:

  • 有關更多信息,我建議你閱讀SO質疑How to capture System.exit event?
  • 系統的Java類的鏈接代碼的OpenJDK 6〜B14,而我的是甲骨文1.6.0_37,但我在發現無差異資源。
  • 我認爲Eclipse沒有顯示線程狀態正確,Thread-0肯定應該在BLOCKED狀態,因爲它試圖獲取一個監視器(參見代碼示例中的here)。不知道DestroyJavaVM線程,我不會假設沒有做線程轉儲。
+1

+1。很好的答案。很好的瞭解關閉掛鉤上的死鎖。 – kosa

+0

@林斯基:真棒,感謝澄清! – PKK

+0

@PKK np,這讓我很感興趣。如果你覺得它有幫助,我可以建議你upvote答案? – linski