天天看點

【多線程核心技術】---線程通信

一:等待/通知機制

    線程與線程之間不是獨立的個體,它們之間可以互相通信和協作。

   1: 不需要等待/通知機制實作線程間的通信:

   2:等待/通知機制

        多個線程之間可以實作通信,原因是多個線程共同通路同一個變量,但這種機制不是“等待/通知”,兩個線程完全是主動地去讀取一個共享變量,在花費讀取時間的基礎上,讀到的值不是想要的,不能完全确定。是以需要一種“等待/通知”機制來滿足需求。

   3:等待/通知機制的實作

    方法wait()的作用是使目前執行代碼的線程進行等待,wait()是Object類的方法,該方法用來将目前線程置于“預執行隊列”中,并且wait()所在的代碼行處停止執行 ,直到通知被中斷為止。

    在調用wait()之前,線程必須獲得該對象的對象級别鎖,即隻能在同步方法或者同步塊中調用wait()方法。

    在執行wait()方法後,目前線程釋放鎖,抛出IllegalMonitorStateException,不需try-catch語句進行捕獲異常。

    方法notify()也需在同步方法或者同步塊中調用,即在調用前,線程也必須獲得該對象的對象級别鎖。如果在調用notify()時沒有持有适當的鎖,也會抛出IllegalMonitorStateException。

    該方法用來通知那些可能等待該對象的對象鎖的其他線程,如果有多個線程等待,則由線程規劃器随機挑出其中一個呈wait狀态的線程,對其發出通知notify,并使其等待擷取該對象的對象鎖。

    執行notify()方法後,目前線程不會馬上釋放該對象鎖,呈wait狀态的線程也也并不能馬上擷取該對象鎖,要等到notify()方法的線程将程式執行完,也就是出synchronized代碼塊後,目前線程才會釋放鎖,而呈wait狀态所在的線程才可以擷取該對象鎖。當第一次獲得該對象鎖的wait線程運作完畢以後,它會釋放該對象鎖,如果此時該對象沒有再次使用notify語句,則即該對象已經空閑,其他wait狀态等待的線程由于沒有得到該對象的通知,還會繼續阻塞在wait狀态,直到這個對象發出一個notify或notifyAll。(即notify方法一次隻能喚醒一個鎖)

    wait使線程停止運作,而notify使停止的線程繼續執行。

    關鍵字synchronized可以将任何一個Object對象作為同步對象來看待,Java為每個對象都實作了wait()和notify()方法,它們必須用在被synchronized同步的Object的臨界區内。

    wait()方法可以使調用該方法的線程釋放共享資源的鎖,然後從運作狀态退出,進入等待隊列,直到被再次喚醒。

    notify()方法可以随機喚醒等待隊列中等待同一共享資源的“一個”線程”,并使該線程退出等待隊列,進入可運作狀态,這就是notify()方法僅通知“一個”線程。

    notifyAll()方法可以使所有正在等待隊列中等待同一資源的“全部”線程從等待狀态退出,進入可運作狀态。優先級高的那個線程最先執行,但也有可能随機執行,則取決于JVM虛拟機。

【多線程核心技術】---線程通信
【多線程核心技術】---線程通信

4:方法wait()鎖釋放與notify()鎖不釋放

    當方法wait()被執行後,鎖被自動釋放,但執行完notify()方法,鎖卻不會馬上釋放。

    sleep()方法不釋放鎖。

    目前對象線程(Lock)在同步代碼塊中執行notify(),線程對象(Lock)不會馬上釋放鎖,而是必須執行完notify()方法所在的同步synchronized代碼塊後才釋放鎖。

5:當interrupt方法遇到wait()方法

    當線程呈wait()狀态時,調用線程對象的interrupt()方法會出現InterruptedException異常。

但是wait()和sleep()都可以通過interrupt()方法打斷線程的暫停狀态,進而使線程立刻抛出InterruptedException。

如果線程A希望立即結束線程B,則可以對線程B對應的Thread執行個體調用interrupt方法。如果此刻線程B正在wait/sleep/join,則線程B會立刻抛出InterruptedException,在catch() {} 中直接return即可安全地結束線程。

需要注意的是,InterruptedException是線程自己從内部抛出的,并不是interrupt()方法抛出的。對某一線程調用interrupt()時,如果該線程正在執行普通的代碼,那麼該線程根本就不會抛出InterruptedException。但是,一旦該線程進入到wait()/sleep()/join()後,就會立刻抛出InterruptedException。

    Thread.interrupt() VS Thread.stop()

  這兩個方法最大的差別在于:interrupt()方法是設定線程的中斷狀态,讓使用者自己選擇時間地點去結束線程;而stop()方法會在代碼的運作處直接抛出一個ThreadDeath錯誤,這是一個java.lang.Error的子類。是以直接使用stop()方法就有可能造成對象的不一緻性。

6:隻通知一個線程

    方法notify()僅随機喚醒一個線程,

    在多次調用notify()方法時,會随機将等待wait狀态的線程進行喚醒。多次調用notify()方法會喚醒全部WAITING中的線程

7:喚醒全部線程

    notifyAll()方法會喚醒全部線程。

8:方法wait(long)的使用

    帶參數的wait(long)方法的功能是将等待某一時間内是否有線程對鎖進行喚醒,如果逾時則自動喚醒。

9:通知過早

   如果先通知notify()方法 thread.sleep(),則wait()方法後的代碼即不會被執行。(等待被喚醒---》方法wait永遠不會被通知)或者notify()方法改變了對象的值,使其wait()方法的同步代碼塊不符合條件,則不執行。

   如果先wait()方法 thread.sleep(),則notify()方法執行完synchronized代碼塊後的代碼,再去執行wait()後的代碼。

10:等待wait的條件發生變化

    在使用wait()/notify()模式時,如果wait()的條件發生變化,則會造成程式邏輯的混亂。

    用while()隻要條件不成立就不會執行,少用 i f { }判斷。

     if 與 while判斷條件的合理使用

11:生産者/消費者模式實作

    等待/通知模式最經典案例就是“生産者/消費者”模式。其原理都是基于wait/notify。

    1)一生産與一消費:操作值

        建立兩個線程對象,一個生産者線程,一個消費者線程。

    2)多生産與多消費:操作值---假死

        線程全部進入WAITING狀态,則程式就不會再執行業務功能了,整個項目呈停止狀态。即“假死”。

        呈假死狀态的程序中所有的線程都呈WAITING狀态。

        代碼中通過wait/notify進行通信,但不能保證notify喚醒的是異類或者同類。則線程不能繼續運作下去,大家都在等待,則呈WAITING狀态。

    3)多生産與多消費者:操作值

        notify()改為notifyAll()通知所有類,防止“假死”狀态。

    4)一生産與一消費:操作棧

        生産者使用堆棧List對象中放入資料,消費者從List堆棧中取出資料,List容量為1。

    5)一生産與多消費---操作棧:解決wait條件改變與假死

         使用一個生産者向堆棧List對象中放入資料,而多個消費者從List堆棧中取出資料,List容量為1。

    6)多生産與一消費:操作棧

        使用生産者向堆棧List對象中放入資料,而多個消費者從List堆棧中取出資料,List容量為1。

    7)多生産與多消費:操作棧

         使用生産者向堆棧List對象中放入資料,而多個消費者從List堆棧中取出資料,List容量為1。

12:通過通道進行線程間的通信:位元組流

    管道流:用在不同線程間直接傳輸資料,實作不同線程間的通信。

        1)PipedInputStream和PipedOutputStream

              PipedInputStream   inputStream=new PipedInputStream()

              PipedOutputStream   outputStream=new PipedOutputStream()

        讀取線程new ThreadRead(outputStream)啟動。使用inputStream。connect(outputStream)或outputStream(inputStream)實作兩個Stream之間的通信連接配接。

13:通過通道進行線程間的通信:字元流

       1)PipedReader和PipedWriter

14:等待/通知之交叉備份

    原理:volatile private Boolean prevIsA=false;

二:方法join的使用

     很多情況下,主線程建立并啟動子線程,如果子線程中要進行大量的耗時運算,主線程往往早于子線程結束。如果主線程需要子線程的資料則需要join()方法。     方法join的作用是使所屬的線程對象 x 正常執行run()方法中的任務,而使目前線程 z 進行無限制的阻塞,等待線程 x 銷毀後再繼續執行 z 後面的代碼。

    方法join()的作用是等待線程對象的銷毀。方法join具有使線程排隊運作的作用,類似于同步的運作效果。

    join與synchronized的差別:

    join在内部使用wait()方法進行等待,而synchronized關鍵字使用的是“對象螢幕”原理作為同步。

    在join過程中,如果目前線程對象被中斷,則目前線程出現異常,

    方法join(long)設定等待的時間

    join()與sleep()方法對同步處理上的差別:

    方法join(long)的功能在内部使用wait(long)方法來實作的,是以方法join()方法具有釋放鎖的特點。而sleep(long)方法不釋放鎖。

    方法join()後面的代碼提前運作:出現意外。

三:類ThreadLocal的使用

    public static 變量的形式可以實作變量值的共享, 所有的線程可以使用同一個public static 變量。

    類ThreadLocal實作每一個線程都有自己的共享變量。

    類ThreadLocal主要解決的是使每個線程綁定自己的值,可以将ThreadLocal類比喻為全局存放資料的盒子,盒子中可以存儲每個線程的私有資料。

    類ThradLocal解決的是變量在不同線程間的隔離性,也就是不同線程擁有自己的值。及使每個線程都有自己的私有屬性。

    get()傳回null 問題———》讓實作類繼承ThreadLocal類,并覆寫initialValue()方法,使其方法具有初始值,傳回初始值。

    子線程和父線程各有各自所擁有的的值,不存在繼承關系。

四:類InheritableThreadLocal的使用

    使用類 InheritableThreadLocal可以在子線程中取得父線程繼承下來的值。

 注意:如果使用InheritableThreadLocal類,字線程在取得值的同時,父線程将InheritableThreadLocal中的值進行更改,那麼子線程的值還是舊值不會改變。

以下文章來自:http://blog.csdn.net/linxdcn/article/details/72819817?fps=1&locationNum=13

線程狀态

public enum State {
        NEW,
        RUNNABLE,
        BLOCKED,
        WAITING,
        TIMED_WAITING,
        TERMINATED;
    }
           
  • NEW 狀态是指線程剛建立, 尚未啟動
  • RUNNABLE 狀态是線程正在正常運作中, 當然可能會有某種耗時計算/IO等待的操作/CPU時間片切換等, 這個狀态下發生的等待一般是其他系統資源, 而不是鎖, Sleep等
  • BLOCKED 這個狀态下, 是在多個線程有同步操作的場景, 這個事件将在另一個線程放棄了這個鎖的時候發生,也就是這裡是線程在等待進入臨界區
  • WAITING(無線等待) 這個狀态下是指線程擁有了某個鎖之後, 調用了他的wait方法, 等待其他線程/鎖擁有者調用 notify / notifyAll 一遍該線程可以繼續下一步操作, 這裡要區分 BLOCKED 和 WATING 的差別, 一個是在臨界點外面等待進入, 一個是在臨界點裡面wait等待别人notify, 線程調用了join方法 join了另外的線程的時候, 也會進入WAITING狀态, 等待被他join的線程執行結束
  • TIMED_WAITING 這個狀态就是有限的(時間限制)的WAITING, 一般出現在調用wait(long), join(long)等情況下, 另外一個線程sleep後, 也會進入TIMED_WAITING狀态
  • TERMINATED 這個狀态下表示 該線程的run方法已經執行完畢了, 基本上就等于死亡了(當時如果線程被持久持有, 可能不會被回收)

java層次的狀态轉換圖

【多線程核心技術】---線程通信

作業系統層次的狀态轉換圖

【多線程核心技術】---線程通信

(2)wait()和notify/notifyAll()方法

wait()方法

  • 線程進入WAITING狀态,并且釋放掉它所占有的“鎖标志”,進而使别的線程有機會搶占該鎖,等待其他線程調用“鎖标志“對象的notify或notifyAll方法恢複
  • wait方法是一個本地方法,其底層是通過一個叫做螢幕鎖的對象來完成的,是以調用wait方式時必須擷取到monitor對象的所有權即通過Synchronized關鍵字,否則抛出IllegalMonitorStateException異常

notify/notifyAll()方法

  • 在同一對象上去調用notify/notifyAll方法,就可以喚醒對應對象monitor上等待的線程了。notify和notifyAll的差別在于前者隻能喚醒monitor上的一個線程,對其他線程沒有影響,而notifyAll則喚醒所有的線程

(3)sleep/yield/join方法解析

sleep

  • sleep方法的作用是讓目前線程暫停指定的時間(毫秒)
  • wait方法依賴于同步,而sleep方法可以直接調用
  • sleep方法隻是暫時讓出CPU的執行權,并不釋放鎖。而wait方法則需要釋放鎖

yield

  • yield方法的作用是暫停目前線程,以便其他線程有機會執行,不過不能指定暫停的時間,并且也不能保證目前線程馬上停止
  • yield隻能使同優先級或更高優先級的線程有執行的機會

join

  • 等待調用join方法的線程結束,再繼續執行。如:t.join(),主要用于等待t線程運作結束
  • 作用是父線程等待子線程執行完成後再執行,換句話說就是将異步執行的線程合并為同步的線程

(4)不推薦使用方法解釋

參考:Why Are Thread.stop, Thread.suspend, Thread.resume and Runtime.runFinalizersOnExit Deprecated?

suspend()和resume()

  • 這兩個方法是配套使用的,suspend()是暫停線程,但并不釋放資源,容易造成死鎖情況

stop()

  • 因為調用stop會使線程釋放所有的鎖,導緻不安全情況,在調用stop時候,由鎖保護的臨界區可能處于狀态不一緻的情況,這不一緻狀态将暴露給其他線程
  • 推薦的做法是,維護一個狀态變量,當線程需要停止時更改這一狀态變量,該線程應檢查這一狀态變量,看該線程是否應該終止了

(5)關于interrupt()中斷函數

  • 其實調用這個函數并不是真的中斷線程,這個函數隻是将Thread中的interrupt标志設定為true,使用者需自行檢測這一變量,停止線程,這種做法避免了stop帶來的問題

推薦:http://blog.csdn.net/zolalad/article/details/38903911