1. 程式人生 > 其它 >Java中為什麼notify()可能導致死鎖,而notifyAll()則不會(針對生產者-消費者模式)

Java中為什麼notify()可能導致死鎖,而notifyAll()則不會(針對生產者-消費者模式)

1、先說兩個概念:鎖池等待池

  • 鎖池:假設執行緒A已經擁有了某個物件(注意:不是類)的鎖,而其它的執行緒想要呼叫這個物件的某個synchronized方法(或者synchronized塊),由於這些執行緒在進入物件的synchronized方法之前必須先獲得該物件的鎖的擁有權,但是該物件的鎖目前正被執行緒A擁有,所以這些執行緒就進入了該物件的鎖池中。
  • 等待池:假設一個執行緒A呼叫了某個物件的wait()方法,執行緒A就會釋放該物件的鎖後,進入到了該物件的等待池中

2、然後再來說notify和notifyAll的區別

  • 如果執行緒呼叫了物件的 wait()方法,那麼執行緒便會處於該物件的等待池
    中,等待池中的執行緒不會去競爭該物件的鎖
  • 當有執行緒呼叫了物件的 notifyAll()方法(喚醒所有 wait 執行緒)或 notify()方法(只隨機喚醒一個 wait 執行緒),被喚醒的的執行緒便會進入該物件的鎖池中,鎖池中的執行緒會去競爭該物件鎖。也就是說,呼叫了notify後只要一個執行緒會由等待池進入鎖池,而notifyAll會將該物件等待池內的所有執行緒移動到鎖池中,等待鎖競爭
  • 優先順序高的執行緒競爭到物件鎖的概率大,假若某執行緒沒有競爭到該物件鎖,它還會留在鎖池中,唯有執行緒再次呼叫 wait()方法,它才會重新回到等待池中。而競爭到物件鎖的執行緒則繼續往下執行,直到執行完了 synchronized 程式碼塊,它會釋放掉該物件鎖,這時鎖池中的執行緒會繼續競爭該物件鎖。

之前寫過一個案例:Java多執行緒— —執行緒 虛假喚醒 問題剖析

解釋一下原因:

Data類中的increment和decrement方法都是同步方法,所以多個呼叫過increment和decrement方法的執行緒都會進入等待池處於阻塞狀態,等待一個正在執行的執行緒來喚醒它們。下面分別分析一下使用notify和notifyAll方法喚醒執行緒的不同之處:

使用了notify方法進行喚醒,而notify方法只能喚醒一個執行緒,其它等待的執行緒仍然處於wait狀態,假設呼叫increment方法的執行緒執行完後,所有的執行緒都處於等待狀態,此時又執行了notify方法,這時如果喚醒的是一個

increment方法的排程執行緒,那麼while迴圈等於true,則此喚醒的執行緒也會處於等待狀態,此時所有的執行緒都處於等待狀態,那麼也就沒有了執行的執行緒來喚醒它們,這就發生了死鎖。

如果使用notifyAll方法來喚醒所有正在等待該鎖的執行緒,那麼所有的執行緒都會處於執行前的準備狀態(就是increment方法執行完後,喚醒了所有等待該鎖的狀態,注:不是wait狀態),那麼此時,即使再次喚醒一個increment方法排程執行緒,while迴圈等於true,喚醒的執行緒再次處於等待狀態,那麼還會有其它的執行緒可以獲得鎖,進入執行狀態。


總結:notify方法很容易引起死鎖,除非你根據自己的程式設計,確定不會發生死鎖,notifyAll方法則是執行緒的安全喚醒方法。