如何在學習Java過程中實現線程之間的通信!

wait,notify 和 notifyAll,這些在多線程中被經常用到的保留關鍵字,在實際開發的時候很多時候卻並沒有被大家重視,而本文則是對這些關鍵字的使用進行描述。

存在即合理

在java中,每個對象都有兩個池,鎖池(monitor)和等待池(waitset),每個對象又都有wait、notify、notifyAll方法,使用它們可以實現線程之間的通信,只是平時用的較少。

  • wait(): 使當前線程處於等待狀態,直到另外的線程調用notify或notifyAll將它喚醒

  • notify(): 喚醒該對象監聽的其中一個線程(規則取決於JVM廠商,FILO,FIFO,隨機…)

  • notifyAll(): 喚醒該對象監聽的所有線程

鎖池: 假設T1線程已經擁有了某個對象(注意:不是類)的鎖,而其它的線程想要調用該對象的synchronized方法(或者synchronized塊),由於這些線程在進入對象的synchronized方法之前都需要先獲得該對象的鎖的擁有權,但是該對象的鎖目前正被T1線程擁有,所以這些線程就進入了該對象的鎖池中。

等待池: 假設T1線程調用了某個對象的wait()方法,T1線程就會釋放該對象的鎖(因為wait()方法必須出現在synchronized中,這樣自然在執行wait()方法之前T1線程就已經擁有了該對象的鎖),同時T1線程進入到了該對象的等待池中。如果有其它線程調用了相同對象的notifyAll()方法,那麼處於該對象的等待池中的線程就會全部進入該對象的鎖池中,從新爭奪鎖的擁有權。如果另外的一個線程調用了相同對象的notify()方法,那麼僅僅有一個處於該對象的等待池中的線程(隨機)會進入該對象的鎖池.

注意事項

  • 在調用wait(), notify()或notifyAll()的時候,都必須獲得某個對象(注意:不是類)的鎖。

  • 永遠在循環(loop)裡調用 wait 和 notify,而不是在 If 語句中

  • 永遠在synchronized的函數或對象裡使用wait、notify和notifyAll,不然Java虛擬機會生成 IllegalMonitorStateException。

使用案例 - 生產消費

如何在學習Java過程中實現線程之間的通信!

分析: 從日誌中可以看到數據會出現多次生產或多次消費的問題,因為在線程執行過程中,兩個線程缺少協作關係,都是各幹各的,T1線程只管生產數據,不管T2線程是否處理了。

改進方案 - wait/notify

上文已經介紹了使用wait和notify的前提了,接下來看案例

如何在學習Java過程中實現線程之間的通信!

如何在學習Java過程中實現線程之間的通信!

分析: 一切都是那麼完美,在T1線程中,調用LOCK.wait()將當前線程移入等待池中,並交出執行權,鎖池中的其他線程去競爭並取得鎖的使用權(T2線程獲取),當T2線程消費完畢後,調用LOCK.notify()方法通知當前對象鎖等待池中的其中一個線程(因為這裡notify是基於JVM算法而定,因為我們只有兩個線程,所以T1線程會接收到T2線程發出的通知,從而繼續生產數據。

問題: 雖然一對一沒有問題,但假設多個生產者多個消費者的情況下怎麼辦呢?

BUG - 埋點

如何在學習Java過程中實現線程之間的通信!

分析: 居然不執行了,藉助前面說過的 死鎖分析知識,我們看看是不是發生死鎖了

如何在學習Java過程中實現線程之間的通信!

結果表明,雖然沒有Found one deadlock...字眼,但是可以看到有個線程都被wait住了,沒有被釋放,所以導致我們當前無法繼續生產消費

解決方案 - notifyAll

如何在學習Java過程中實現線程之間的通信!

分析: 這裡只修改了一句代碼,就是將consumer方法中的notify -> notifyAll,由通知單個線程變成通知所有在等待池中的線程

如何在學習Java過程中實現線程之間的通信!

Java程序員學習交流群515675832,既有技術大佬,又有老司機開車,各位對Java感興趣的可以來交流學習一下,快樂與技術一起進步

相關推薦

推薦中...