併發系列(一)-----synchronized關鍵字

一 簡介

    說到併發不得不提的synchronized,synchronized關鍵字是元老級別的角色。在Java SE 1.6以前synchronized被稱爲是重量,在1.6以後對同步進行了一系列的優化,使它的「重量」發生變化。這篇文章主要介紹同步的原理和它「重量」變化html

二 表現形式

    同步代碼在表現的形式有三種同步在代碼的表現形式有三種編程

    1.對於同步方法,鎖是當前實例對象(非靜態方法)數組

    2.對於靜態同步方法,鎖是當前類的類對象(靜態方法)安全

    3.對於同步方法塊,鎖是sysnchronized括號裏配置的對象(代碼塊)數據結構

 三 原理說明

   在JVM(1.7)規範裏面就說明了同步的原理多線程

原文以下:Java虛擬機中的同步(同步)基於進入和退出管程(監視器)對象實現不管是顯式同步(有明確的monitorenter和monitorexit指令)仍是隱式同步(依賴方法調用和返回指令實現的)都是如此。在Java的語言中,能夠被同步修飾的同步方法。標誌來隱式實現的(參見§2.11.10「同步」)。併發

    monitorenter和monitorexit指令用於實現同步語句塊,譬如須要執行其對應的指令,而不管這個方法是正常結束(§2.6.4)仍是異常結束(§2.6.5)。爲了保證在方法異常完成時monitorenter和monitorexit指令依然能夠正確配對執行,編譯器會自動產生一個異常處理器(§2.10),這個異常處理器聲明可處理全部的異常,它的目的就是用來執行monitorexit指令具體博客:JVM規範說明jvm

    從上面JVM規範中能夠看出JVM基於進入和退出監測對象來實現方法同步和代碼塊同步,二者的實現細節不同。代碼塊同步是使用monitorenter和monitorexit指令實現的,用指令讀取運行時常量池中方法的ACC_SYNCHRONIZED標誌來隱式實現的.monitorenter 指令是在編譯後插入到同步代碼塊的開始位置,而monitorexit是插入到方法結束處理和異常處理,JVM要保證每一個monitorenter必須有對應monitorexit與之配對。任何對象都有一個監視與之關聯,當且一個顯示器被持有後,它將處於鎖定狀態。線程執行到monitorenter指令時,將會嘗試獲取對象所對應的監控的全部權,即嘗試得到對象的鎖。ide

 四 鎖的重量變化

Java SE 1.6爲了減小得到鎖和釋放鎖帶來的性能消耗,引入了「偏向鎖」和「輕量級鎖」,在Java SE 1.6中,鎖一共有4種狀態,級別從低到高依次是:無鎖狀態,偏向鎖狀態,輕量級鎖狀態和重量級鎖狀態,這幾個狀態會隨着競爭狀況逐漸升級鎖能夠升級但不能降級,意味着偏向鎖升級成輕量級鎖後不能降級成偏向鎖性能

4.1 對象頭和棧幀的簡單介紹

在介紹鎖的重量變化的前讀者可能要明白一些關於JVM的相關知識這裏爲了好理解只是簡單的介紹一下對象頭和棧幀。

棧幀:方法在執行的同時都會建立一個棧幀(方法運行時的基礎數據結構)用於存儲局部變量表,操作數棧,動態連接,方法出口等信息每個方法從調用直至執行完成的過程,就對應這一個棧幀在虛擬機棧中入棧到出棧的過程。局部變量表存放了編譯器可知的各類基本數據類型,對象引用(regerence,它不等同與對象自己,多是一個指向對象起始地址的引用指針,也多是指向一個表明對象的句柄或其餘於此對象相關的位置)和returnAddress類型(指向了一條字節碼指令的地址)

對象頭:HotSpot虛擬機的對象頭(Object Header)分爲兩部分信息,第一部分用於存儲對象自身的運行時數據,如哈希碼(HashCode),GC分代年齡(Generational GC Age)等,這部分數據的長度在32位和64位的虛擬機中分別爲32個和64個位,官方稱它爲「Mark Word」,它是實現輕量級鎖和偏向鎖的關鍵。另一部分用於存儲指向方法區對象類型數據的指針,若是是數組對象的話,還會有一個額外的部分用於存儲數組長度。

          對象頭信息是與對象自身定義的數據無關的額外存儲成本,考慮到虛擬機的空間效率,Mark Word被設計成一個非固定的數據結構以便在極小的空間內存儲儘可能多的信息,它會根據對象的狀態複用本身的存儲空間例如。在32位的熱點虛擬機中對象未被鎖定的狀態下,標記字的32個位空間中的25位用於存儲對象哈希碼(HashCode),4位用於存儲對象分代年齡,2位用於存儲鎖標誌位,1位固定爲0,在其餘狀態(輕量級鎖定,重量級鎖定,GC標記,可偏向)下對象的存儲內容如表所示。

4.2 偏向鎖

偏向鎖的「偏」,就是偏愛的「偏」,偏袒的「偏」。它的意思是這個鎖會偏向於第一個得到它的線程,若是在接下來的執行過程當中,該鎖沒有被其餘的線程獲取,則持有偏向鎖的線程將永遠不須要再進行同步這裏。這點能夠從偏向鎖的的撤銷體現出來偏向鎖的撤銷採用的是一種等到競爭出現才釋放的機制,也就是說若是沒有競爭那麼偏向鎖一直都不會釋放。

4.2.1鎖定獲取

當一個線程訪問同步塊並獲取鎖時,會在對象頭(包含運行時數據和類型指針)和棧幀(一種數據結構)中的鎖記錄裏存儲鎖偏向的線程ID,之後該線程在進入和退出同步塊時不須要進行CAS操做來加鎖和解鎖,只需簡單地測試一下對象頭的Mark Word裏是否存儲着指向當前線程的偏向鎖。若是測試成功,表示線程已經得到了鎖。若是測試失敗,則須要再測試一下標記字中偏向鎖的標識是否設置成1(表示當前是偏向鎖):若是沒有設置,則使用CAS競爭鎖;若是設置了,則嘗試使用CAS將對象頭的偏向鎖指向當前線程。

過程以下

(1)訪問Mark Word中偏向鎖的標識是否設置成1,鎖標誌位是否爲01--確認爲可偏向狀態。

(2)若是爲可偏向狀態,則測試線程ID是否指向當前線程,若是是,進入步驟(5),不然進入步驟(3)。

(3)若是線程ID並未指向當前線程,則經過CAS操做競爭鎖若是競爭成功,則將標誌字中線ID設置爲當前線程ID,而後執行(5);若是競爭失敗,執行(4)。

(4)若是CAS獲取偏向鎖失敗,則表示有競爭。當到達全局安全點(還原點詳細信息參考JVM虛擬機)時得到偏向鎖的線程被掛起,偏向鎖升級爲輕量級鎖,而後被阻塞在安全點的線程繼續往下執行同步代碼。

(5)執行同步代碼。

4.2.2偏向鎖的撤銷

偏向鎖使用了一種等到競爭出現才釋放鎖的機制,因此當其餘線程嘗試競爭偏向鎖時,持有偏向鎖的線程纔會釋放鎖。偏向鎖的撤銷,須要等待全局安全點(在這個時間點上沒有正在執行的字節碼)它會首先暫停擁有偏向鎖的線程,而後檢查持有偏向鎖的線程是否活着,若是線程不處於活動狀態,則將對象頭設置成無鎖狀態;若是線程仍然活着,擁有偏向鎖的棧會被執行,遍歷偏向對象的鎖記錄,棧中的鎖記錄和對象頭的標記字要麼從新偏向於其餘線程,要麼恢復到無鎖或者標記對象不適合做爲偏向鎖(這時就須要鎖升級了),最後喚醒暫停的線程。

4.3輕量級鎖

 輕量級鎖是JDK 1.6之中加入的新型鎖機制,它名字中的「輕量級」是相對於使用操做系統互斥量來實現的傳統鎖而言的,所以傳統的鎖機制就被稱爲「重量級」鎖。首先須要強調一點的是,輕量級鎖並非用來代替重量級鎖的,它的本意是在沒有多線程競爭的前提下,減小傳統的重量級鎖使用操做系統互斥量產生的性能消耗。

4.3.1輕量級的加鎖

         線程在執行同步塊以前,JVM會先在當前線程的第一個中創用於存儲鎖記錄的空間,並將對象頭中的Mark Word複製到鎖記錄中,官方稱爲DisplacedMark Word。而後線程嘗試若是成功,當前線程得到鎖,若是失敗,表示其餘線程競爭鎖,當前線程便嘗試使用自旋來獲取鎖。

4.3.2輕量級鎖是解鎖

輕量級解鎖時,會使用原子的CAS操做將位移標記字替換回對象頭,若是成功,則表示沒有競爭發生。若是失敗,表示當前鎖存在競爭,鎖就會膨脹成重量級鎖。

由於自旋會消耗CPU,爲了不無用的自旋(好比得到鎖的線程被阻塞住了),一旦鎖升級

成重量級鎖,就不會再恢復到輕量級鎖狀態。當鎖處於這個狀態下,其餘線程試圖獲取鎖時,

都會被阻塞住,當持有鎖的線程釋放鎖以後會喚醒這些線程,被喚醒的線程就會進行新一輪

的奪鎖之爭。

 

五 鎖的有比較

 

六 說明

文章有差錯地方但願你們指出,郵箱alemand@163.com

 

參考文檔:

<< Java併發編程藝術>>方騰飛魏鵬程曉明

<<深刻理解Java虛擬機:JVM高級特性與最佳實踐>>周志明

<< Java虛擬機規範JavaSE1.7 >> Tim Lindholm,Frank Yellin,  Gilad Bracha,Alex Buckley

https://www.tuicool.com/articles/2aeAZn

https://www.artima.com/insidejvm/ed2/threadsynch3.html

http://www.iteye.com/topic/1018932

相關文章
相關標籤/搜索