2013-05-01 77 views
1

我是想實現一些基本的啓動,停止,暫停和恢復功能,可以讓我下面的狀態轉換:Java線程的原因死鎖

  • 停止運行
  • 運行到停止
  • 運行以暫停
  • 暫停運行
  • 停下來,停止(原因死鎖)

大多數情況下按預期運行,但最後的狀態轉換不可行,因爲它會使線程凍結。有人能解釋我爲什麼會發生這種情況,以及如何預防這種情況?下面是代碼的相關部分:

public class ThreadTest implements Runnable { 

    private volatile boolean running = false; 
    private volatile boolean paused = false; 
    private Thread thread; 

    public ThreadTest() { 
     thread = new Thread(this); 
    } 

    public void run() { 
     while (running) { 
      try { 
       if (paused) { 
        synchronized (this) { 
         while (paused) 
         wait(); 
        } 
       } 
      } 
      catch (InterruptedException e) { 
      } 
     } 
    } 

    public synchronized void start() { 
     if(running && !thread.isAlive()) 
      return; 
     running = true; 
     thread = new Thread(this); 
     thread.start(); 
    } 

    public synchronized void stop() { 
     if(!running && thread.isAlive()) 
      return; 
     running = false; 
     try { 
      thread.join(); 
     } catch (InterruptedException e) { 
      e.printStackTrace(); 
     } 
     System.exit(0);  
    } 

    public synchronized void resume() { 
     if(paused) { 
     paused = false; 
     notify(); 
     } 
     else { 
      return; 
     } 
    } 

    public synchronized void pause() { 
     if(!paused) { 
      paused = true; 
      } 
      else { 
       return; 
      } 
     } 

} 
+0

嘗試調用''中的情況下paused'通知()''中的stop()'方法。你通過wait()調用來鎖定你的線程。 – SeniorJD 2013-05-01 14:53:24

+0

@SeniorJD:請參閱我的答案和我對xagyg的評論,爲什麼notify()不能工作。 – brainOverflow 2013-05-02 19:07:20

+0

+1因爲你的問題對我有用(讓我覺得)! – brainOverflow 2013-05-02 19:10:04

回答

3

wait();在run方法將一直等待監守這些不notify(); 當通話停止,線程運行監守永遠等下去,所以thread.join()將鎖定。 你需要停止來電通知或更改等待有史以來wait(1000);

+0

等待(1000)將不起作用。這是因爲,當等待調用超時時,運行run()的可運行線程(讓這個調用爲T2)將自動嘗試並重新獲取ThreadTest對象上的鎖(它在進入等待之前釋放)。但是,這個鎖已經被主線程(在stop()方法中,它正等待T2線程上的join())持有!所以,即使等待(1000)超時,它也不會在run()方法內恢復執行。 – brainOverflow 2013-05-02 18:22:49

+0

em ...我認爲你是對的。謝謝 – andy 2013-05-03 08:32:10

1
  1. stop方法,調用只是thread.notify();running = false;。 (這將通知等待的線程)。

  2. 然後你必須設置paused = false;就在你的通知電話之前。

  3. 從您的run方法中刪除if (paused)塊。

  4. 將您的while (paused)環路更改爲while (paused && running)。或者,您可以使用while (paused) { wait(); if (!running) break;},具體取決於您想要的控制流程。

  5. 良好的措施,所述volatile關鍵字添加到pausedrunning變量聲明(以創建跨線程存儲器柵欄)。

+0

這沒有奏效,因爲當我嘗試在暫停時結束/退出時,應用程序仍然處於掛起狀態。 – user1812379 2013-05-02 09:29:31

+0

請參閱我的答案下面爲什麼通知()將無法正常工作! notify()可能不起作用的另一個原因是T1在T2進入等待之前執行stop()(並因此還有notify())!這導致錯過喚醒! – brainOverflow 2013-05-02 18:57:13

+0

@ user1812379您需要從運行方法中刪除「if(paused)」塊。 – xagyg 2013-05-02 22:19:44

1

讓我們看看究竟發生在這裏: 讓我們的名字在這裏參與爲T2和T1(其中調用啓動,在停止方法(您明確實例化,並在你的代碼開始的線程)的線程T2線程對象)。 T1可能是您的主線程,取決於您的其他未顯示的代碼。

你得到一個死鎖,因爲下面的事件序列: (注1:這只是一個可能的序列,有可能在這個代碼其他可能的序列,這也可能導致死鎖)

說我們的ThreadTest對象上做一個start(),暫停(),然後一個stop()如下(假設在main()):

ThreadTest t = new ThreadTest(); 
t.start(); 
t.pause(); 
t.stop(); 
  1. 暫停過後()執行T1, T2獲得了鎖定通過在「if(paused)」條件中輸入「synchronized(this)」塊來獲得ThreadTest對象。 (注2:「此」在這裏是指不將T2線程對象,但它是指ThreadTest對象的run()是在ThreadTest類的方法。)

  2. T2進入等待()並在它進入wait()調用時釋放ThreadTest對象鎖(隱式地)。

  3. 當T1進入stop()時,由於stop()是一個同步方法,所以它獲取ThreadTest對象的鎖定。在stop()中,T1調用t2.join(),並等待T2完成。 但T2已經處於等待狀態(),並且沒有人將其喚醒!

因此死鎖!即使我們通過在wait()調用中指定超時或通過調用notify()(如其他人所建議的)來喚醒T2,它仍然無法退出等待狀態,因爲它無法重新啓動,獲取(隱式地)鎖定(在ThreadTest對象上),因爲T1已經在join()中等待T1了!

一個可能的解決方案: 雖然可能有很多可能的解決方案,你可以試試這個嗎?

在stop()方法,而不是

thread.join(); 

你可以使用:

if (!paused) { 
    thread.join(); 
} else { 
    thread.interrupt(); 
} 
+0

感謝您的詳細解釋。還有一個問題:爲什麼在這裏不能使用單獨的鎖對象? – user1812379 2013-05-02 10:13:39

+0

一個單獨的鎖對象在哪裏?你能否爲你想要的東西添加更多細節? – brainOverflow 2013-05-02 17:36:37