當前位置:編程學習大全網 - 編程語言 - 線程間的通信方式

線程間的通信方式

線程間的通信方式:簡單講就是在鎖、堆裏的對象包括普通對象個原子變量,他們之間可以實現線程間的通信。

1、通過***享對象通信

線程間發送信號的壹個簡單方式是在***享對象的變量裏設置信號值。線程A在壹個同步塊裏設置boolean型成員變量hasDataToProcess為true,線程B也在同步塊裏讀取hasDataToProcess這個成員變量。線程A和B必須獲得指向壹個MySignal***享實例的引用,以便進行通信。如果它們持有的引用指向不同的MySingal實例,那麽彼此將不能檢測到對方的信號。需要處理的數據可以存放在壹個***享緩存區裏,它和MySignal實例是分開存放的。

2、忙等待(Busy Wait)

準備處理數據的線程B正在等待數據變為可用。換句話說,它在等待線程A的壹個信號,這個信號使hasDataToProcess()返回true,線程B運行在壹個循環裏,以等待這個信號。

3、wait(),notify()和notifyAll()

忙等待沒有對運行等待線程的CPU進行有效的利用,除非平均等待時間非常短。否則,讓等待線程進入睡眠或者非運行狀態更為明智,直到它接收到它等待的信號。

Java有壹個內建的等待機制來允許線程在等待信號的時候變為非運行狀態。java.lang.Object 類定義了三個方法,wait()、notify()和notifyAll()來實現這個等待機制。

壹個線程壹旦調用了任意對象的wait()方法,就會變為非運行狀態,直到另壹個線程調用了同壹個對象的notify()方法。為了調用wait()或者notify(),線程必須先獲得那個對象的鎖。也就是說,線程必須在同步塊裏調用wait()或者notify()。以下是MySingal的修改版本——使用了wait()和notify()的

等待線程將調用doWait(),而喚醒線程將調用doNotify()。當壹個線程調用壹個對象的notify()方法,正在等待該對象的所有線程中將有壹個線程被喚醒並允許執行(校註:這個將被喚醒的線程是隨機的,不可以指定喚醒哪個線程)。同時也提供了壹個notifyAll()方法來喚醒正在等待壹個給定對象的所有線程。

如妳所見,不管是等待線程還是喚醒線程都在同步塊裏調用wait()和notify()。這是強制性的!壹個線程如果沒有持有對象鎖,將不能調用wait(),notify()或者notifyAll()。否則,會拋出IllegalMonitorStateException異常。

但是,這怎麽可能?等待線程在同步塊裏面執行的時候,不是壹直持有監視器對象(myMonitor對象)的鎖嗎?等待線程不能阻塞喚醒線程進入doNotify()的同步塊嗎?答案是:的確不能。壹旦線程調用了wait()方法,它就釋放了所持有的監視器對象上的鎖。這將允許其他線程也可以調用wait()或者notify()。

壹旦壹個線程被喚醒,不能立刻就退出wait()的方法調用,直到調用notify()的線程退出了它自己的同步塊。換句話說:被喚醒的線程必須重新獲得監視器對象的鎖,才可以退出wait()的方法調用,因為wait方法調用運行在同步塊裏面。如果多個線程被notifyAll()喚醒,那麽在同壹時刻將只有壹個線程可以退出wait()方法,因為每個線程在退出wait()前必須獲得監視器對象的鎖。

4、丟失的信號(Missed Signals)

notify()和notifyAll()方法不會保存調用它們的方法,因為當這兩個方法被調用時,有可能沒有線程處於等待狀態。通知信號過後便丟棄了。因此,如果壹個線程先於被通知線程調用wait()前調用了notify(),等待的線程將錯過這個信號。這可能是也可能不是個問題。不過,在某些情況下,這可能使等待線程永遠在等待,不再醒來,因為線程錯過了喚醒信號。

為了避免丟失信號,必須把它們保存在信號類裏。在MyWaitNotify的例子中,通知信號應被存儲在MyWaitNotify實例的壹個成員變量裏。

留意doNotify()方法在調用notify()前把wasSignalled變量設為true。同時,留意doWait()方法在調用wait()前會檢查wasSignalled變量。事實上,如果沒有信號在前壹次doWait()調用和這次doWait()調用之間的時間段裏被接收到,它將只調用wait()。

(校註:為了避免信號丟失, 用壹個變量來保存是否被通知過。在notify前,設置自己已經被通知過。在wait後,設置自己沒有被通知過,需要等待通知。)

5、假喚醒

由於莫名其妙的原因,線程有可能在沒有調用過notify()和notifyAll()的情況下醒來。這就是所謂的假喚醒(spurious wakeups)。無端端地醒過來了。

如果在MyWaitNotify2的doWait()方法裏發生了假喚醒,等待線程即使沒有收到正確的信號,也能夠執行後續的操作。這可能導致妳的應用程序出現嚴重問題。

為了防止假喚醒,保存信號的成員變量將在壹個while循環裏接受檢查,而不是在if表達式裏。這樣的壹個while循環叫做自旋鎖 (校註:這種做法要慎重,目前的JVM實現自旋會消耗CPU,如果長時間不調用doNotify方法,doWait方法會壹直自旋,CPU會消耗太大) 。被喚醒的線程會自旋直到自旋鎖(while循環)裏的條件變為false。

留意wait()方法是在while循環裏,而不在if表達式裏。如果等待線程沒有收到信號就喚醒,wasSignalled變量將變為false,while循環會再執行壹次,促使醒來的線程回到等待狀態。

6、多個線程等待相同信號

如果妳有多個線程在等待,被notifyAll()喚醒,但只有壹個被允許繼續執行,使用while循環也是個好方法。每次只有壹個線程可以獲得監視器對象鎖,意味著只有壹個線程可以退出wait()調用並清除wasSignalled標誌(設為false)。壹旦這個線程退出doWait()的同步塊,其他線程退出wait()調用,並在while循環裏檢查wasSignalled變量值。但是,這個標誌已經被第壹個喚醒的線程清除了,所以其余醒來的線程將回到等待狀態,直到下次信號到來。

  • 上一篇:零基礎學習Java的書籍有哪些,請推薦,謝謝
  • 下一篇:可轉債投資的可轉債投資的策略
  • copyright 2024編程學習大全網