Java虛擬機器(十)記憶體模型與執行緒
JAVA記憶體模型
Java的記憶體模型的主要目標是定義程式中各個變數的訪問規則,即在虛擬機器中將變數儲存到記憶體和從記憶體中取出變數這樣的底層細節。這裡說的變數包含了例項欄位,靜態欄位和構成資料物件的元素(共享的),而不包括區域性變數和方法引數,因為他們是執行緒私有的。
Java記憶體模型規定了所有變數都儲存在主記憶體中,每個執行緒還有自己的工作記憶體,,工作記憶體中儲存了被該執行緒使用到的變數的主記憶體副本拷貝,執行緒對變數的操作都必須在工作記憶體中進行。各個工作記憶體是互相隔離的,執行緒之間的變數值傳遞是通過主記憶體來完成。
對於主記憶體和工作記憶體之間具體的互動協議,java記憶體模型定義了8中操作來完成,每一項操作都是原子性的。
volatile關鍵字是Java虛擬機器提供的最輕量級的同步機制。當一個變數被定義為volatile之後,他講具備兩種特性,一種是保證此變數對所有執行緒的可見性(一條執行緒修改了這個變數的值,其他執行緒可以立即得知),普通變數是做不到這點的,因為普通變數的值傳遞需要通過主記憶體來完成。
volatile變數在各個執行緒中是一致的,但基於volatile的運算在併發下並不是安全的。因為他只能保證可見性,並不能保證原子性。
下面這類場景就很適合使用volatile做控制鬢髮,當shutdown()方法被呼叫時,能保證執行緒中執行的doWork()方法立刻停下來。
volatile第二個語意是禁止指令重排優化。普通變數只能保證所有依賴賦值結果的地方都能獲取到正確的結果,而不能保證執行順序。volatile可以。
原子性,可見性(volatile的保證可見性的規則是使新值立即同步到主記憶體,每次使用時從主記憶體重新整理,synchronized和final也可以保證)與有序性(有序性是指在計算機內部指令的執行順序,volatile和synchronized關鍵字能夠保證有序性)。
Java實現執行緒有三種方式,使用核心執行緒實現,使用使用者執行緒實現和使用者執行緒加輕量級程序混合實現。
使用者執行緒的建立,同步,銷燬和排程完全在使用者態中完成,不需要核心的幫助。如果程式實現得當,這種執行緒不需要切換到核心態,因此這種操作是非常快速且低消耗的。
不需要系統核心的支援意味著,“阻塞該如何處理”,“多處理器系統中如何將執行緒對映到其他處理器上”這樣的問題需要自己負責。
對於Sun JDK來說,他的windows版與Linux版都是使用一對一的執行緒模型實現的,一條Java執行緒就對映到一條輕量級的程序之中。
Java執行緒排程,執行緒排程是指系統為執行緒分配處理器使用權的過程,分為協同式排程和搶佔式排程。Java使用的執行緒排程方式是搶佔式排程。Java可以通過設定優先順序來建議系統給執行緒多分配一點執行時間。當執行緒出於Ready狀態時,優先順序越高越容易被系統選擇執行。
Java語言定義了5中執行緒狀態,在任意一個時間點,一個執行緒有且僅有其中一種狀態。
新建(New):建立後尚未啟動的執行緒出於這種狀態。
執行(Runnable):Runnable包括了作業系統執行緒狀態中的Running和Ready,就是執行緒有可能正在執行,也有可能正等待著CPU為他分配時間片。
無限期等待(Waiting):出於這種狀態的執行緒不會被分配CPU執行時間,他們要等待其他執行緒顯式地喚醒。
有限期等待(Timed Waiting):出於這種狀態的執行緒不會被分配CPU執行時間,一定時間後會被自動喚醒。
阻塞(Blocked):阻塞狀態出於等待獲取鎖的狀態。
結束(Terminated):已終止執行緒的執行緒狀態,執行緒已經結束執行。
執行緒安全的實現方法
互斥同步是常見的一種併發正確性保障手段,同步是指在多個執行緒併發訪問共享資料時,保證共享資料在同一時刻只能被一個執行緒使用,互斥是實現同步的一種手段(臨界區,互斥量,訊號量)。
Java中最進本的互斥手段就是synchronized關鍵字。除了synchronized之外,我們還可以使用java.util.concurrent包中的重入鎖來實現同步。重入鎖增加了一些高階功能,等待可中斷,可實現公平說,以及鎖可以繫結多個條件。
等待可中斷是指正在等待的執行緒可以選擇放棄等待,改為處理其他事情。公平鎖是指按照申請鎖的時間順序來依次獲得鎖。鎖繫結多個條件是指一個重入鎖可以同時繫結多個條件物件。
非阻塞同步
因為互斥同步會帶來執行緒阻塞和喚醒所帶來的效能問題,所以也被成為阻塞同步。互斥同步屬於一種悲觀鎖的併發策略,總是認為會有人競爭。
我們還有另一種選擇,基於衝突檢測的樂觀併發策略。先進行操作,如果沒有其他執行緒爭用共享資料,那操作就成功了。如果有爭用,不斷重試,直到成功為止。
樂觀鎖常見的有CAS,CAS需要三個運算元,分別是記憶體位置,舊的預期值,新值。當且僅當記憶體中的值符合舊值時採用新值替換他,否則就不更新。也有漏洞,ABA問題。
鎖優化
自旋鎖與自適應自旋。如果執行緒獲取不到鎖的時候需要掛起和恢復對效能會帶來很大的壓力。我們可以讓後面請求鎖的那個執行緒“稍等一下”,可以讓執行緒執行一個忙迴圈(自旋)。
後面JDK中還引入了自適應自旋,意味著自旋等待的時間不在固定,而是由前一次在同一個鎖上的自旋時間以及鎖的擁有者的狀態來決定。
鎖消除
鎖消除是指虛擬機器在編譯器啟動時對不可能存在共享資料競爭的鎖進行消除。
鎖粗化
原則上,在編寫程式碼時總是推薦獎同步塊的範圍儘可能縮小。但是如果一些列操作都是對同一個物件反覆加鎖和解鎖,即使沒有競爭,對於效能還是會有很大的損耗。這時會將鎖的範圍擴大。
輕量級鎖
輕量級是相對於使用作業系統互斥量來實現的傳統鎖而言的,傳統的鎖機制稱為“重量級”鎖。輕量級鎖並不是用來代替重量級鎖的,本意是在沒有多執行緒競爭的前提下,減少傳統的重量級鎖使用作業系統互斥量產生的消耗。
在HotSpot虛擬機器的物件頭Mark Word裡會有當前是輕量級和重量級鎖的標識。
虛擬機器將使用CAS操作嘗試將物件的Mark Word更新為指向Lock Record的指標,如果成功了,就擁有這個物件的鎖。並且標記這個物件為輕量級鎖的狀態。
如果這個更新失敗了,虛擬機器將會檢查物件的Mark word是否指向當前的棧幀,如果有兩條以上的執行緒爭用同一個鎖,那輕量級鎖就不在有效,要膨脹為重量級鎖。
偏向鎖
它的意思是這個鎖會偏向於第一個獲得它的執行緒,如果在接下來的執行過程中,該鎖沒有被其他執行緒獲取,則持有該偏向鎖的執行緒永遠不需要再進行同步。