Thread源碼分析之join方法引伸--wait 爲什麼必定要寫在同步代碼塊中

對於wait的使用,JDK中給出的解釋以下

In other words,
* waits should always occur in loops, like this one:
* <pre>
*     synchronized (obj) {
*         while (<condition does not hold>)
*             obj.wait(timeout);
*         ... // Perform action appropriate to condition
*     }
* </pre>
* (For more information on this topic, see Section 3.2.3 in Doug Lea's
* "Concurrent Programming in Java (Second Edition)" (Addison-Wesley,
* 2000), or Item 50 in Joshua Bloch's "Effective Java Programming
* Language Guide" (Addison-Wesley, 2001).
app

就是說wait使用的套路以下

     synchronized (obj) {
         while (<condition does not hold>)
             obj.wait(timeout);
         ... // Perform action appropriate to condition
     }ide

wait使用的套路總結

  1. 必須在 synchronized 同步方法或者 synchronized 同步塊中
  2. 必須在while循環中使用

爲何wait必須在 synchronized 同步方法或者 synchronized 同步塊中?

反證舉例:對象o的wait(notify、notifyAll)若是不在synchronized 同步方法或者 synchronized 同步塊,那麼線程I調用o.wait()時,線程II能夠(同一時間內)隨時調用o.notify(),以下圖函數

線程I處於o.wait()調用後的wait set中,此時代碼運行的指針暫存了起來。oop

由於沒有互斥同步,線程II能夠隨時調用o.notify(),那麼無論線程I中的condition條件是否知足,都會發生【①通知繼續】。ui

隨着【①通知繼續】的發生,線程II認爲:我已經通知過線程I,讓線程I繼續運行了,個人任務完成了!this

線程I中接收到【①通知繼續】,而後會從暫存的位置取出代碼運行指針,繼續往o.wait()後面運行,而後又跑到while(condition)這裏。剛纔說了,condition條件可能還不符合,那麼就又進入了o.wait(),暫存起來運行指針,線程I進入對象o的wait set區。spa

如今是什麼狀況?線程II認爲:我已經通知過線程I,讓線程I繼續運行了,個人任務完成了!因此線程II不會再次通知了。線程

線程I呢?如上所述再次進入了對象o的wait set,由於線程II不會再次notify線程I,那麼線程I將永遠存在於wait set區域,再也出不來了!設計

因此爲啥要同步o.wait()和o.notify()?就是爲了互斥執行,只有condition知足時,才讓線程II通知線程I從wait set區跳出來繼續執行,進而繼續執行後續的代碼。指針

爲何wait必須在while循環中使用?

若是wait方法不在while循環中使用,那麼通知wait結束,沒有再次進行條件判斷就繼續wait後續代碼。可是有可能出現condition並不知足的狀況,此時代碼邏輯就亂了。

★根本緣由--wait函數的設計思路

首先wait方法是屬於類Object。能夠認爲任何一個Object對象都有一個內置鎖,由於Object類是全部類的祖先類,也就是說全部類的對象都有一個內置鎖。想調用wait、notify、notifyAll方法,必需要先獲取對象的內置鎖。

wait被設計時就是要和notify(或者notifyAll)配合使用的。

使用場景就是:對象o,其有一個屬性爲a。屬性a被線程I和線程II共享,也就是說a爲線程I和線程II的共享變量。當a爲true時,線程I調用o的wait。線程II能夠改變o中a的狀態,改變後調用o的notify方法,通知線程I跳出o的wait方法,繼續向後執行代碼。

整體來講,人家設計的就是這個套路。

t.join() 是誰拿了誰的鎖?

以前說過。實質上調用代碼1就至關於調用代碼2。分析join的源碼,結合wait的使用套路,能夠獲得這個結論。

假設線程I調用t.join()。t.join()最終調用的是t.wait()。

t.join()方法是synchronized 方法。因此線程I中運行到t.join(),是線程I拿到了對象t的鎖。

將t.join()換成代碼2,也應該以對象t爲鎖。因此代碼2中,仍然是線程I拿到了對象t的鎖。

相關文章
相關標籤/搜索