JVM高級特性與最佳實踐——第十二章 Java內存模型與線程

Java內存模型JMM,主要目標是定義程序中各個變量的訪問規則,即在虛擬機中將變量存儲到內存和從內存讀取變量的底層細節,這裡的變量不包括線程私有的變量,如局部參數;內存模型規定所有變量存儲在主內存;每個線程都有自己的工作內存,其中保存了該線程用到的變量的主內存的副本;注意,線程對變量的所有操作都必須在工作內存中進行,不能直接讀寫主內存的變量;

內存間的交互動作,即工作內存與主內存之間的拷貝同步,Java規定下述8中操作完成兩者之間的拷貝與同步,並保證是原子性的(long、double例外),注意,read和load、store和write是順序執行的,但不代表指令是連續執行的:

1、鎖定lock,作用於主內存變量,將其標識為線程獨佔

2、解鎖unlock,作用於主內存,從鎖定狀態解鎖,釋放後的變量才能被其他線程鎖定

3、讀取read,作用於主內存,從主內存傳遞到工作內存,以便load

4、加載load,作用於工作內存,將read讀回的變量值放入工作內存的變量副本中

5、使用use,作用於工作內存,將工作內存的變量值傳遞給執行引擎,虛擬機在遇到執行引擎需要使用某個變量時會執行這個操作

6、賦值assign,作用於工作內存,將從執行引擎收到的值賦給工作內存中變量,虛擬機遇到給變量賦值的字節碼時會執行這個操作

7、存儲store,作用於工作內存,將本地內存的值傳遞給主內存,以便write

8、寫入write,作用於工作內存,將從store操作從工作內存中得到的變量值寫回主內存變量中

上述8中操作還必須滿足下列條件:

1、不允許read和load、store和write之一出現,即必須是成對出現

2、不允許線程丟棄最近的assign操作,即變量在工作內存中的改變必須寫回主內存

3、不允許線程無緣故(沒有assign操作)地將數據從工作內存寫回主內存

4、一個新變量只能在主內存中產生,不允許在工作內存中直接使用一個沒有初始化的變量,即use、store之前,必須執行過load、assign

5、一個變量只允許被同一個線程lock,允許多次lock,lock次數等於unlock的次數

6、對一個變量執行lock,將會清空工作內存中此變量的值,執行引擎使用改變量之前,需要重新load或assign操作以初始化改值

7、不允許對一個沒有lock的變量進行unlock操作,也不允許一個線程unlock另一個線程鎖住的變量

8、對變量執行unlock之前,必須將變量同步會主內存

volatile類型,輕量級的同步機制,具有如下兩種特性

1、保證此變量對所有線程的可見性,即某個線程改變了這個變量的值,新值對於其他線程來說是立即可見的,volatile只保證可見性,在不滿足下列情況時,仍需要使用同步

1、計算結果不依賴當前值,或者保證只有一個線程可以改變這個值

2、變量不需要其他狀態變量共同參與不變約束,即同步更新問題

2、volatile禁止語義重排

long、double的特殊性,雖然虛擬機允許將long、double這些64位的讀寫操作劃分為兩個32為操作,即在多線程情況下可能出現讀到“半個值”的情況,但實際開發中,商用虛擬機都是將其實現為院子操作,因此這兩類類型在開發時不必特別聲明為volatile

Java內存模型主要圍繞如何實現原子性、可見性、有序性特徵建立:

1、原子性,read、load、use、assign、store、write保證了基本變量的原子性,對於更大範圍的原子性,可以圖通過使用synchronized實現

2、可見性,Java通過在變量修改後將新值同步回住內存,在變量讀取前從主內存刷新變量值這種依賴主內存作為傳遞媒介的方式實現可見性,無論是否使用volatile關鍵字,而普通變量與volatile修飾的變量區別在於volatile保證新值能立即同步到主內存,每次使用前立即從主內存刷新

3、有序性,如果從線程內部觀察,所有操作串行執行,而從線程之間觀察,操作是無序的(指令重排與工作內存和主內存同步延遲)

先行發生原則,如果操作A先行發生於操作B,即發生在操作B之前,那麼操作A產生的影響能被操作B觀察到,這裡的影響包括修改共享變量的值、發送消息、調用方法等;通過先行發生原則,無須同步器協助,就能保證線程安全,如果兩者不滿足先行原則,並不能由如下規則導出,則處理器可以任意重排指定:

1、程序次序原則,書寫在前面的程序先於後面的程序發生,即線程中控制流的順序

2、管程鎖定原則,對於同一個鎖對象,unlock先行發生於後面對同一個鎖的lock

3、volatile變量原則,對一個volatile變量的寫操作先行發生於讀操作

4、線程啟動原則,Thread對象的start()先行與線程的每個動作

5、線程終止原則,線程的所有操作先行與該線程的終止檢測

6、線程中斷原則,對線程的interrupt()操作先行發生於被中斷線程的代碼檢測到中斷事件的發生

7、對象終結原則,一個對象的初始化完成(構造函數執行結束)先行發生於finalize()方法的開始

8、傳遞性,如果A先行於B,B先行於C,則A先行於C

注意,時間先後的順序和先行原則發生之間沒有太大關係,即併發安全問題必須以先行發生原則為準

線程,是進程輕量級的調度執行單位,使得資源分配與線程調度分開,既可以共享資源,又可以獨立調度(線程是CPU調度最小單位),線程的實現主要有三種方式:

1、內核線程實現(kernel-level Thread),直接由操作系統內核支持的線程,由內核通過調度器完成線程調度、將線程映射到各個處理器中;每個內核線程可以看做內核的分身;程序一般使用內核線程的高級接口——輕量級進程LWT(即線程),LWT和內核線程是一對一關係;在這裡,每個LWT就是調度的基本單位,侷限性在於,線程所有操作由內核完成,而系統調用的代價較高,需要在內核態和用戶態之間切換,並且LWT和內核線程是一對一的,因此LWT消耗內核資源,一個系統支持的LWT數量數有限的

2、用戶線程實現,狹義上,用戶線程完全建立在用戶空間的線程庫上,內核不能感知線程的存在,線程的建立、同步、銷燬完全在用戶態完成,無須內核參與,這裡進程與線程是一對多的關係;優勢在於,不需要內核支援,操作快速低消耗;劣勢在於,沒有內核支援,用戶進程需要考慮線程的所有操作,實現複雜,並且處理器資源只能分配到進程

3、用戶線程加內核線程混合實現,兩者兼有,用戶線程還是在用戶空間,線程的創建、切換等依然高效,支持大規模併發;操作系統提高線程調度和處理器映射,輕量級進程作為用戶線程和內核線程的橋樑,降低了整個進程被阻塞的風險;這裡用戶線程和輕量級線程之間關係是多對多;

Java線程實現:1.2及以後,線程模型為基於操作系統原生線程模型來實現,即操作系統支持什麼線程模型,決定了虛擬機支持什麼線程模型

Java線程調度:線程調度是指為線程分配處理器使用權限的過程;主要分為兩種方式:

1、協作式線程調度,線程執行時間由線程自身控制,執行完成後通知系統切換到另一個線程;優點是實現簡單,不存在線程安全性問題,缺點是線程執行時間不可控

2、搶佔式線程調度,線程執行由系統分配時間,線程的切換不是線程自身決定;優點是執行時間可控,不會出現一個線程導致整個進程阻塞問題;Java使用的就是這種線程調度方式

Java線程優先級,java雖然設置了10個優先級,但是Java的線程是通過映射到系統原生線程上來實現的,線程的調度取決於系統,系統優先級和Java優先級不一定一一對應,並且優先級還可能被系統改變

Java線程狀態,共五種狀態,一個線程在任意時間點,有且僅有其中一種狀態:

1、新建new,創建尚未啟動

2、運行runnable,包括操作系統的running和ready狀態,可能正在執行,可也能在等待CPU時間

3、無限期等待waiting,不會分配CPU時間,必須由其他線程顯式喚醒,如未設置時間的wait()、join();發生在線程進入同步區

4、限期等待timed waiting,不會被分配CPU時間,但是無須其他線程顯式喚醒,超時將右系統自動喚醒,如設置時間的wait()、join();發生在線程進入同步區

5、阻塞blocked,線程在等待一個排它鎖,事件將在一個線程放棄鎖的時候發生

6、結束terminated,終止的線程,線程結束執行

JVM高級特性與最佳實踐——第十二章 Java內存模型與線程


分享到:


相關文章: