Java多線程之線程協作


Java多線程之線程協作

一、前言

  上一節提到,如果有一個線程正在運行synchronized 方法,那么其他線程就無法再運行這個方法了。這就是簡單的互斥處理。

  假如我們現在想執行更加精確的控制,而不是單純地等待其他線程運行終止,例如下面這樣的控制。

  ● 如果空間為空則寫入數據;如果非空則一直等待到變空為止

  ● 空間已為空時,“通知”正在等待的線程

  此處是根據“空間是否為空”這個條件來執行線程控制的。Java 提供了用於執行線程控制的wait 方法、notify 方法和notifyAll 方法。wait 是讓線程等待的方法,而notify 和notifyAll 是喚醒等待中的線程的方法。

二、等待隊列——線程休息室

  在學習wait、notify 和notifyAll 之前,我們先來學習一下等待隊列。所有實例都擁有一個等待隊列,它是在實例的wait方法執行后停止操作的線程的隊列。打個比方來說,就是為每個實例准備的線程休息室。

  在執行wait 方法后,線程便會暫停操作,進入等待隊列這個休息室。除非發生下列某一情況,否則線程會一直在等待隊列中休眠。當下列任意一種情況發生時,線程便會退出等待隊列。

  ● 有其他線程的notify方法來喚醒線程

  ● 有其他線程的notifyAll方法來喚醒線程

  ● 有其他線程的interrupt方法來喚醒線程

  ● wait方法超時

  下面以圖配文依次談談wait、notify 和notifyAll。而關於interrupt 方法和wait 方法的超時,將會在后面的篇幅中談談。

三、wait 方法——將線程放入等待隊列

  wait(等待)方法會讓線程進入等待隊列。假設我們執行了下面這條語句。

  obj.wait();

  那么,當前線程便會暫停運行,並進入實例obj的等待隊列中。這叫作“線程正在obj 上wait”。如果實例方法中有如下語句(1),由於其含義等同於(2),所以執行了wait() 的線程將會進入this 的等待隊列中,這時可以說“線程正在this 上wait”。

  wait();  (1)

  this.wait(); (2)

  若要執行wait方法,線程必須持有鎖(這是規則)。但如果線程進入等待隊列,便會釋放其實例的鎖。整個操作過程如下圖所示。

  • 關於等待隊列

  等待隊列是一個虛擬的概念。它既不是實例中的字段,也不是用於獲取正在實例上等待的線程的列表的方法。

  • 獲取鎖了的線程A執行wait方法:

  • 線程A進入等待隊列,釋放鎖:

  • 線程B能夠獲取鎖:

四、notify 方法——從等待隊列中取出線程

  notify(通知)方法會將等待隊列中的一個線程取出。假設我們執行了下面這條語句。

  obj.notify();

  那么obj 的等待隊列中的一個線程便會被選中和喚醒,然后就會退出等待隊列。

  整個操作過程如下所示。

  • 獲取鎖了的線程B執行notify方法:

  • 線程A退出等待隊列,想要進入wait的下一個操作,但剛才執行notify方法的線程B任持有着鎖

  • 剛才執行notify的線程B釋放了鎖

  • 退出等待隊列的線程A獲取鎖,執行wait的下一步操作

  同wait 方法一樣,若要執行notify 方法,線程也必須持有要調用的實例的鎖(這是規則)。

  執行notify 后的線程狀態:

  notify 喚醒的線程並不會在執行notify 的一瞬間重新運行。因為在執行notify 的那一瞬間,執行notify 的線程還持有着鎖,所以其他線程還無法獲取這個實例的鎖(如第二幅圖所示)。

  執行notify 后如何選擇線程?

  假如在執行notify 方法時,正在等待隊列中等待的線程不止一個,對於“這時該如何來選擇線程”這個問題規范中並沒有作出規定。究竟是選擇最先wait 的線程,還是隨機選擇,或者采用其他方法要取決於Java 平台運行環境。因此編寫程序時需要注意,最好不要編寫依賴於所選線程的程序。

五、notifyAll 方法——從等待隊列中取出所有線程

  notifyAll(通知大家)方法會將等待隊列中的所有線程都取出來。例如,執行下面這條語句之后,在obj 實例的等待隊列中休眠的所有線程都會被喚醒。

  obj.notifyAll();

  如果簡單地在實例方法中寫成下面(1)這樣,那么由於其含義等同於(2),所以該語句所在方法的實例(this)的等待隊列中所有線程都會退出等待隊列。

  notifyAll();  (1)

  this.notifyAll(); (2)

  下面兩幅圖展示了notify 方法和notifyAll 方法的差異。notify 方法僅喚醒一個線程,而notifyAll 則喚醒所有線程,這是兩者之間唯一的區別。

  • notify方法僅喚醒一個線程,並讓該線程退出等待對列:

  • notifyAll方法喚醒所有線程,並讓所有線程都退出等待隊列

  同wait 方法和notify 方法一樣,notifyAll 方法也只能由持有要調用的實例的鎖的線程調用。

  剛被喚醒的線程會去獲取其他線程在進入wait 狀態時釋放的鎖。但現在鎖是在誰的手中呢?對,就是執行notifyAll 的線程正持有着鎖。因此,喚醒的線程雖然都退出了等待隊列,但都在等待獲取鎖,處於阻塞狀態。只有在執行notifyAll 的線程釋放鎖以后,其中一個幸運兒才能夠實際運行。

  那如果線程未持有鎖會怎樣呢?

  如果未持有鎖的線程調用wait、notify 或notifyAll, 異常java.lang.IllegalMonitorStateException會被拋出。

  該使用notify 方法還是notifyAll 方法呢?

  notify 方法和notifyAll 方法非常相似,到底該使用哪一個呢?實際上,這很難選擇。

  由於notify 喚醒的線程較少,所以處理速度要比使用notifyAll 時快。

  但使用notify 時,如果處理不好,程序便可能會停止。一般來說,使用notifyAll 時的代碼要比使用notify 時的更為健壯。

  除非開發人員完全理解代碼的含義和范圍,否則使用notifyAll 更為穩妥。使用notify時發生問題的示例將在后面探討,詳情可以關注我的博文。

六、wait、notify、notifyAll 是Object 類的方法

  wait、notify 和notifyAll 都是java.lang.Object 類的方法,而不是Thread 類中固有的方法。

  下面再來回顧一下wait、notify 和notifyAll 的操作。

  ● obj.wait()是將當前線程放入obj的等待隊列中

  ● obj.notify()會從obj的等待隊列中喚醒一個線程

  ● obj.notifyAll()會從obj的等待隊列中喚醒所有線程

  換句話說, wait、notify 和notifyAll 這三個方法與其說是針對線程的操作,倒不如說是針對實例的等待隊列的操作。由於所有實例都有等待隊列,所以wait、notify 和notifyAll也就成為了Object 類的方法。

  wait、notify、notifyAll 也是Thread 類的方法:

  wait、notify 和notifyAll 確實不是Thread 類中固有的方法。但由於Object 類是Java 中所有類的父類,所以也可以說wait、notify 和notifyAll 都是Thread 類的方法。關於wait、notify 和notifyAll 的用法,后面的篇幅中將會詳細解說。

 

  參考:圖解Java多線程設計模式


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM