《深刻理解Java虛擬機》筆記--第十二章、Java內存模型與線程

主要內容:虛擬機如何實現多線程、多線程之間因爲共享和競爭數據而致使的一系列問題及解決方案。
Java內存模型:
    Java內存模型的主要目標是定義程序中各個變量的訪問規則,即在虛擬機中將變量存儲到內存和從內存中取出變量這樣的底層細節。(這裏所說的變量是指可能存在競爭問題的實例字段、靜態字段和構成數據對象的元素)
     Java內存模型規定了全部的變量都存儲在虛擬機內存的主內存(Main Memory)中,每條線程還有本身的工做內存(Working Memory),線程的工做內存中保存了被該線程使用到的變量的主內存副本拷貝,線程對變量的全部操做都必須在本身的工做內存中進行,而不能直接讀寫主內存中的變量。
  
    Java內存模型定義了8種操做來完成主內存和工做內存之間的交互:
        做用於主內存:lock(鎖定)、unlock(解鎖)、read(讀取)、write(寫入)
        做用於工做內存: load(載入)、use(使用)、assign(賦值)、store(存儲)
        規則:須要順序執行read和load、store和write【沒必要保證連續執行】。
                  不容許read和load、store和write操做之一單獨出現。
                  不容許線程丟棄它最近的assign操做,必須同步回主內存。
                  不容許線程無緣由(沒有發生過任何assign操做)的把數據同步回主內存。
                  新變量只能在主內存中誕生,不容許在工做內存中直接使用一個未初始化(load或assign)的變量。
                  一個變量同一時刻只容許一條線程對其進行lock操做,可屢次lock,須要該線程進行等量的unlock操做纔會被解鎖。
                  若是對一個變量執行lock操做,將會清除工做內存中此變量的值,在使用此變量前須要從新初始化到工做內存中。
                  unlock操做必須在lock操做以後,且不可跨線程。
                  對一個變量unlock操做前,必須先把變量同步回主內存中。
volatile型變量的特殊規則:
    關鍵字volatile是虛擬機提供的最輕量級的同步機制。
    volatile變量具有的兩種特性:a)變量對因此線程的可見性。b)禁止指令重排序優化。
    volatile變量在大多數場景下,總開銷仍然要比鎖(使用synchronized關鍵字或java.util.concurrent包裏面的鎖)來的低。
對於long和double類型的非原子性操做規則:
     Java內存模型定義的8種操做都具備原子性,可是對64位的數據(long和double)特別定義了一條規則(非原子性協定):容許未被volatile修飾的64位的數據讀寫操做劃分爲兩次32位的操做來進行。
原子性、可見性和有序性:
     Java內存模型是圍繞着在併發過程當中如何處理原子性、可見性和有序性這三個特徵來創建的。
    原子性(Automicity):由Java內存模型來保證的的原子性變量操做包括read(讀取)、write(寫入)、load(載入)、use(使用)、assign(賦值)、store(存儲)這六個。更大範圍的原子性保證須要使用到synchronized關鍵字,即synchronized塊之間的操做也具有原子性。
    可見性(Visibility):當一個線程修改了共享變量的值,其餘線程當即獲得這個修改。Java內存模型是經過在變量修改後將變量同步回主內存,在變量讀取前從主內存刷新變量值這種依賴主內存做爲傳遞媒介的方式來實現可見性的。能夠看作是將read->load->use、assign->store->write組合成兩個原子性操做實現可見性。
    有序性(Ordering):若是在本線程內觀察,全部的操做都是有序的;若是在一個線程中觀察另外一個線程,全部操做都是無序的。無序性主要因爲存在」指令重排序「和」工做內存與主內存同步延遲「。
    volatile能保證可見性,卻不保證有序性,而synchronized能保證三種特性,可是對性能有影響。(PS:volatile是否保證有序性有必定的爭議,參考其餘文章後認爲其不能保證)
先行發生原則:
    程序次序規則(Program Order Rule)、管程鎖定規則(Monitor Lock Rule)、volatile變量規則(Volatile Variable Rule)、線程啓動規則(Thread Start Rule)、線程終止規則(Thread Termination Rule)、線程中斷規則(Thread Interruption Rule)、對象終結規則(Finalizer Rule)、傳遞性(Transitivity)
    先行發生原則是判斷數據是否存在競爭,線程是否安全的主要依據。
    先行發生是Java內存模型中定義的兩項操做之間的偏序關係,若是說操做A先行發生於操做B,也就是說發生操做B以前,操做A產生的影響能被操做B觀察到。
    時間上的前後順序和先行發生原則之間基本沒有太大關係。
線程的實現:
    線程是比進程更輕量級的調度執行單位,各個線程能夠共享進程的資源,又能夠獨立調度(線程是CPU調度的最基本單位)。
    實現線程主要有三種方式:使用內核線程實現,使用用戶線程實現,使用用戶線程加輕量級進程混合實現。
    使用內核線程實現:
        直接由操做系統內核(多線程內核)支持的線程,線程切換由內核來完成,內核通操縱調度器(Scheduler)對線程進行調度,並負責將線程的任務映射到各個處理器上。
        程序通常不會直接去使用內核線程,而是去使用內核線程的一種高級接口——輕量級進程——每一個輕量級進程都有都由一個內核線程支持。
        缺陷:系統調用的代價相對較高(須要在用戶態和內核態中來回切換)、消耗內核資源(如內核線程棧空間)=>一個系統支持輕量級進程的數量是有限的。
    使用用戶線程實現:
        徹底創建在用戶空間的線程庫上,系統內核不能感知到線程存在的實現。用戶線程的創建、同步、銷燬和調度徹底在用戶態中完成,不須要內核的幫助。
        基本不須要切換到內核態,所以操做能夠是很是快速且低消耗的。
        沒有系統內核的支援,全部的線程操做都須要用戶程序本身處理,比較複雜。
    使用用戶線程加輕量級進程混合實現:
        既存在用戶線程,也存在請練級進程。用戶線程能夠支持大規模的用戶線程併發,而操做系統提供支持的輕量級進程則做爲用戶線程和內核線程之間的橋樑。
        混合模式下,用戶線程和輕量級進程的數量比是不定的。
Java線程的實現:
    jdk1.2以前使用的是用戶線程,以後的版本線程模型與平臺相關。
    主流的操做系統都提供了線程實現,Java語言則提供了在不一樣硬件和操做系統平臺下對線程的統一處理,每一個java.lang.Thread類的實例就表明了一個線程。
    Thread類的全部關鍵方法都被聲明爲Native,意味着這些方法沒有使用或沒法使用平臺無關的手段來實現,或者爲了執行效率而使用Native方法。
    對於Sun JDK來講,它的windows版和linux版都是使用一對一的線程模型來實現的,一條Java線程映射到一條輕量級進程之中。
Java線程調度:
    線程調度是指系統爲線程分配處理器使用權的過程,主要有兩種調度方式:協同式線程調度(Cooperation Threads-Scheduling)、搶佔式線程調度(Preemptive Threads-Scheduling)。
    協同式線程調度:
        線程執行時間由線程自己來控制,線程執行完成後,要主動通知系統切換到另外一個線程上去。
        實現簡單,因爲線程切換操做對線程本身是可知的,因此沒有什麼線程同步的問題。
        線程執行時間不可控制,若是一直不通知系統進行線程切換,可能致使整個程序阻塞。
    搶佔式線程調度(目前Java使用的調度方式):
        每一個線程將由系統分配執行時間,線程的切換不禁線程自己來決定(Java中線程能夠提早讓出,但不能夠強制獲取到執行時間)。
        線程的執行時間是系統可控的,不會出現一個線程致使整個進程阻塞的問題。
        經過設置線程的優先級,能夠建議系統給線程分配的時間不一樣。線程優先級高的容易被系統選擇執行,但不保證必定優先選擇執行。
Java線程狀態轉換:
    Java語言定義了5種線程狀態,在任意一個時間點,一個線程只能有且只有其中的一種狀態:新建(New)、運行(Runable)、無限期等待(Waiting)、限期等待(Timed Waiting)、阻塞(Blocked)、結束(Terminated)。
  
相關文章
相關標籤/搜索