1. 程式人生 > >深入理解Java虛擬機器JVM高階特性與最佳實踐閱讀總結—— 第十二章 Java記憶體模型與執行緒

深入理解Java虛擬機器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,終止的執行緒,執行緒結束執行