1. 程式人生 > >Java多執行緒程式設計核心技術 —— 執行緒間通訊

Java多執行緒程式設計核心技術 —— 執行緒間通訊

執行緒是作業系統中獨立的個體,但這些個體如果不經過特殊的處理就不能成為一個整體。執行緒間的通訊就是成為整體的比用方案之一,可以說,使執行緒間進行通訊後,系統之間的互動性會更強大,在大大提高CPU利用率的同時還會使程式設計師對個執行緒任務在處理的過程中進行有效的把控與監督。

1、wait使執行緒停止執行,而notify使停止的執行緒繼續執行。

2、嘗試一下:

出現的原因是沒有“物件監視器”,也就是沒有同步加鎖。解決方案:synchronized(newString),把wait方法放到synchronized塊中。

3、關鍵字synchronized可以將任何一個Object物件作為同步物件來看待,而Java為每個Object都實現了wait()和notify()方法,它們必須用在被synchronized同步的Object的臨界區內。

4、wait()方法可以使呼叫該方法的執行緒釋放共享資源的鎖,然後從執行狀態退出,進入等待佇列,直到被再次喚醒。

      notify()方法可以隨機喚醒等待佇列中等待統一共享資源的“一個”執行緒,並使該執行緒退出等待佇列,進入可執行狀態,也就是notify()方法僅通知“一個”執行緒。

     notifyAll()方法可以使所有正在等待佇列中等待同一共享資源的“全部”執行緒從等待狀態退出,進入可執行狀態。此時,優先順序最高的那個執行緒最先執行,但也有可能是隨機執行,因為這要取決於JVM虛擬機器的實現。

5、執行緒狀態示意圖

1)新建立一個新的執行緒物件後,在呼叫它的start()方法,系統會為此執行緒分配CPU資源,使其處於Runnable(可執行)狀態,這是一個準備執行的階段。如果執行緒搶佔到CPU資源,此執行緒就處於Running(執行)狀態。

2)Runnable狀態和Running狀態可相互切換,因為有可能執行緒執行一段時間後,有其他高優先順序的執行緒搶佔了CPU資源,這時次執行緒就從Running狀態程式設計Runnable狀態。

執行緒進入Runnable狀態大體分為如下5種情況:

  • 呼叫sleep()方法後經過的時間超過了指定的休眠時間。
  • 執行緒呼叫的阻塞IO已經返回,阻塞方法執行完畢。
  • 執行緒成功地獲得了試圖同步的監視器。
  • 執行緒正在等待某個通知,其他執行緒發出了通知。
  • 處於掛起狀態的執行緒呼叫了resume回覆方法。

3)Blocked是阻塞的意思,例如遇到了一個IO操作,此時CPU處於空閒狀態,可能會轉而把CPU時間片分配給其他執行緒,這時也可以稱為“暫停”狀態。Blocked狀態機結束後,進入Runnable狀態,等待系統重新分配資源。

出現阻塞的情況大體分為如下5種:

  • 執行緒呼叫sleep方法,主動放棄佔用的處理器資源。
  • 執行緒呼叫了阻塞式IO方法,在該方法返回前,該執行緒被阻塞。
  • 執行緒試圖獲得一個同步監視器,但該同步監視器正被其他執行緒所持有。
  • 執行緒等待某個通知。
  • 程式呼叫了suspend方法將該執行緒掛起。此方法容易導致死鎖,儘量避免使用該方法。

4)run()方法執行結束後進入銷燬階段,這個執行緒執行完畢。

每個所物件都有兩個佇列,一個是就緒佇列,一個是阻塞佇列。就緒佇列儲存了將要獲取鎖的執行緒,阻塞佇列儲存 被阻塞的執行緒。一個執行緒被喚醒後,才會進入就緒佇列,等待CPU的呼叫;反之,一個執行緒被wait後,就會進入阻塞佇列,等待下一次被喚醒。

6、當方法wati()被執行後,鎖被自動釋放,但執行完notify()方法,鎖卻不自動釋放。

7、方法notify()僅隨機喚醒一個執行緒。當多次呼叫notify()方法時,會隨機將等待wait狀態的執行緒進行喚醒。

8、帶一個引數的wait(long)方法的功能是等待某一事件內是否有執行緒對鎖進行喚醒,如果超過這個時間則自動喚醒。

9、在使用wait/notify模式時,還需要注意另外一種情況,也就是wait等待的條件傳送了變化,也容易造成程式邏輯的混亂。

10、等待/通知模式最經典的案例就是“生產者/消費者”模式。但此模式在使用上有幾種“變形”,還有一些小的注意事項,但原理都是基於wait/notify的。

11、在Java語言中提供了各種各樣的輸入/輸出流Stream,使我們能夠很方便地對資料進行操作,其中管道流(pipeStream)是一個種特殊的流,用於在不同執行緒間直接傳送資料。一個執行緒傳送資料到輸出管道,另一個執行緒從輸入管道中讀資料。通過使用管道,實現不同執行緒間的通訊,而無需藉助於類似臨時檔案之類的東西。

在Java的JDK中提供了4個類來使執行緒間進行通訊:

1)PipedInputStream和PipedOutputStream

2)PipedReader和PipedWriter

Join方法

12、在很多情況下,主執行緒建立並啟動子執行緒,如果子執行緒中要進行大量的耗時計算,主執行緒往往將早於子執行緒結束之前結束。這時,如果主執行緒想等待子執行緒執行完成之後在結束,比如子執行緒處理一個數據,主執行緒要取得這個資料中的值,就要用到join()方法了。方法join()的作用是等待執行緒物件銷燬。

13、方法join的作用是使所屬的執行緒物件x正常執行run()方法中的任務,而使當前執行緒z進行無限期的阻塞,等待執行緒x銷燬後再繼續執行執行緒z後面的程式碼。

14、方法join具有使執行緒排隊執行的作用,有些類似同步的執行效果。join與synchronized的區別是:join在內部使用wait()方法進行等待,而synchronized關鍵字使用的是“物件監視器”原理做為同步。

15、在join過程中,如果當前執行緒物件被中斷,則當前執行緒出現異常。

16、方法join(long)的功能在內部是使用wait(long)方法來實現的,所以join(long)方法具有釋放鎖的特點。當前執行緒鎖被釋放,那麼其他執行緒就可以呼叫此執行緒中的同步方法了。而Thread.sleep(long)方法卻不釋放鎖。

類ThreadLocal的使用

17、變數值的共享可以使用public static變數的形式,所有的執行緒都使用同一個public static變數。如果想實現每一個執行緒都有自己的共享變數該如何解決呢?JDK中提供的類ThreadLocal正式為了解決這樣的問題。

18、類ThreadLocal主要解決的就是每個執行緒繫結自己的值,可以將ThreadLocal類比喻成全域性存放資料的盒子,盒子中可以儲存每個執行緒的私有資料。

19、ThreadLocal類解決的是變數在不同執行緒間的隔離性,也就是不同執行緒擁有自己的值,不同執行緒中的值是可以放入ThreadLocal類中進行儲存的。


類InheritableThreadLocal的使用

20、使用InheritableThreadLocal類可以讓子執行緒從父執行緒中取得值。

21、使用InheritableThreadLocal類需要注意一點的是,如果子執行緒在取得值的同時,主執行緒將InheritableTHreadLocal中的值進行更改,那麼子執行緒取到的值還是舊值。