Java併發編程系列---線程間通信

一、線程間通信

線程開始運行,擁有自己的棧空間,就如同一個腳本一樣,按照既定的代碼一步一步地執行,直到終止。但是,每個運行中的線程,如果僅僅是孤立地運行,那麼沒有一點兒 價值,或者說價值很少,如果多個線程能夠相互配合完成工作,這將會帶來巨大的價值。

二、單線程之間通信

2.1 初識 wait/notify

wait和notify方法並不是Thread特有的方法,而是Object中的方法,也就是說在JDK中的每一個類都擁有這兩
個方法,這兩個方法可以使線程阻塞和喚醒線程。

2.1.1 wait方法

我們先來說說wait方法,下面是wait方法的三個重載方法。

public final void wait() throws InterruptedException
public final void wait(long timeout) throws InterruptedException
public final void wait(long timeout, int nanos) throws InterruptedException
  • wait方法的這三個重載方法都將調用wait(longtimeout)這個方法,前文使用的wait()方法等價於wait(0),0代表着永不超時。
  • Object的wait(longtimeout)方法會導致當前線程進人阻塞,直到有其他線程調用了Object的notify或者notifyAll方法才能將其喚醒,或者阻塞時間到達了timeout時間而自動喚醒。
  • wait方法必須擁有該對象的monitor,也就是wait方法必須在同步方法中使用。
  • 當前線程執行了該對象的wait方法之後,將會放棄對該monitor的所有權並且進入與該對象關聯的wait set中,也就是說一旦線程執行了某個object的wait方法之後,它就會釋放對該對象monitor的所有權,其他線程也會有機會繼續爭搶monitor的所有權。

2.1.2 notify方法

public final native void notify();
  • 喚醒單個正在執行該對象wait方法的線程。
  • 如果有某個線程由於執行該對象的wait方法而進人阻塞則會被喚醒,如果沒有則會忽略。
  • 被喚醒的線程需要重新獲取對該對象所關聯monitor的lock才能繼續執行。

2.2 關於wait和notify的注意事項

  1. wait方法是可中斷方法,這也就意味着,當前線程一旦調用了wait方法進人阻塞狀態,其他線程是可以使用interrupt方法將其打斷的;可中斷方法被打斷後會收到中斷異常InterruptedException,同時interrupt標識也會被擦除。
  2. 線程執行了某個對象的wait方法以後,會加入與之對應的wait set中,每一個對象的monitor都有一個與之關聯的wait set
  3. 當線程進人wait set之後,notify方法可以將其喚醒,也就是從wait set中彈出,同時中斷wait中的線程也會將其喚醒。
  4. 必須在同步方法中使用wait和notify方法,因爲執行wait和notify的前提條件是必須持有同步方法的monitor的所有權,運行下面任何一個方法都會拋出非法的monitor狀態異常IllegalMonitorStateException:
  5. 同步代碼的monitor必須與執行wait notify方法的對象一致,簡單地說就是用哪個對象的monitor進行同步,就只能用哪個對象進行wait和notify操作。

2.3 wait和sleep區別

從表面上看,wait和sleep方法都可以使當前線程進人阻塞狀態,但是兩者之間存在着本質的區別,下面我們將總結兩者的區別和相似之處。

  1. wait和sleep方法都可以使線程進人阻塞狀態。
  2. wait和sleep方法均是可中斷方法,被中斷後都會收到中斷異常。
  3. wait是Object的方法,而sleep是Thread特有的方法。
  4. wait方法的執行必須在同步方法中進行,而sleep則不需要。
  5. 線程在同步方法中執行sleep方法時,並不會釋放monitor的鎖,而wait方法則會釋放monitor的鎖。
  6. sleep方法短暫休眠之後會主動退出阻塞,而wait方法(沒有指定wait時間)則需要被其他線程中斷後才能退出阻塞。

三、多線程進行通信

3.1 notifyAll方法

多線程間通信需要用到Object的notifyAll方法,該方法與notify比較類似,都可以喚醒由於調用了wait方法而阻塞的線程,但是notify方法只能喚醒其中的一個線程,而notifyAll方法則可以同時喚醒全部的阻塞線程,同樣被喚醒的線程仍需要繼續爭搶monitor的鎖。

3.2 線程休息室 wait set

在虛擬機規範中存在一個wait set ( wait set又被稱爲線程休息室)的概念,至於該waitset是怎樣的數據結構,JDK官方並沒有給出明確的定義,不同廠家的JDK有着不同的實現方式,甚至相同的JDK廠家不同的版本也存在着差異,但是不管怎樣,線程調用了某個對象的wait方法之後都會被加入與該對象monitor關聯的waitset中,並且釋放monitor的所有權。
若干個線程調用了wait 方法之後被加入與monitor關聯的wait set中,待另外一個線程調用該monitor的notify方法之後,其中一個線程會從wait set 中彈出,至於是隨機彈出還是以先進先出的方式彈出,虛擬機規範同樣也沒有給出強制的要求notify 方法只會喚醒在wait set中休息的一個線程而執行notifyAll則不需要考慮哪個線程會被彈出,因爲wait set中的所有wait線程都將被彈出。

發佈了68 篇原創文章 · 獲贊 12 · 訪問量 40萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章