synchronized 實現(xiàn)原理與應用

一 、synchronized 實現(xiàn)原理與應用

1.1、synchronized作用

關鍵字synchronized的作用是實現(xiàn)線程間的同步。它的工作是對同步代碼塊區(qū)加鎖,在同一時刻,只有一個線程進入同步代碼塊區(qū),從而保證線程間的安全;

1.2、synchronized用法

  • 指定加鎖對象:對指定對象加鎖,進入臨界區(qū)前要獲得指定對象的鎖;
  • 直接作用于實例方法:相當于對當前實例加鎖,進入臨界區(qū)前要獲得當前實例對象的鎖;
  • 直接作用于靜態(tài)方法:相當于對當前類加鎖,進入臨界區(qū)前要獲得當前類的鎖;

1.3、synchronized實現(xiàn)原理

在多線程并發(fā)中synchronized關鍵字是元老級別的,很多人都稱呼它是重量級鎖。但是Java SE 1.6對synchronized進行了各種優(yōu)化,引入了偏向鎖和輕量級鎖為了減少獲得鎖和釋放鎖帶來的性能消耗。

當一個線程試圖進入同步代碼塊前,必須獲得鎖,退出或拋出異常時必須釋放鎖。

從JVM規(guī)范可以看到synchronized的實現(xiàn)原理,JVM基于進入和退出Monitor對象來實現(xiàn)方法同步和代碼塊同步,但兩者實現(xiàn)的細節(jié)不一樣,代碼塊同步是使用monitorenter和monotorexit指令實現(xiàn),而方法同步使用另一種實現(xiàn),細節(jié)在JVM規(guī)范中沒有詳細說明。同步代碼塊開始位置在編譯后插入monitorenter指令,而同步代碼塊結束和異常的位置插入monitorexit指令。

1.3.1 java對象頭

那么鎖到底是存在哪里?鎖里會存儲怎么樣的信息?

synchronizeds用的鎖是存在java對象頭里的。如果對象是數(shù)組類型,則虛擬機用3個子寬(work)存儲對象頭,如果對象是非數(shù)組類型,則虛擬機用2個子寬(work)存儲對象頭。在32位虛擬機中,1字寬等于4字節(jié),即32bit,如下表:

[圖片上傳失敗...(image-ce11d5-1564208709702)]

java對象頭里的Mark word 里默認存儲對象的Hashcode、分代年齡和鎖標記位。32位JVM的Mark work的默認存儲結構如下表:

image.png

在運行期間,Mark work 里存儲的數(shù)據會隨著鎖標志位的變化而變化。Mark work 可能會存儲以下四種數(shù)據,如下表:

image.png

在64位虛擬機下,Mark work是64bit的大小,其存儲結構如下表:

image.png

1.3.2 鎖的升級與對比

java SE 1.6 為了減少獲得鎖和釋放鎖帶來的性能消耗,引入了"偏向鎖"和"輕量級鎖",在java SE 1.6中,鎖一共有四種狀態(tài),級別從低到高依次是:無鎖狀態(tài)、偏向鎖狀態(tài)、輕量級鎖狀態(tài)、重量級鎖狀態(tài),這幾個狀態(tài)會隨著競爭情況逐漸升級,鎖可以升級當是不可以降級,意味著偏向鎖升級為輕量級鎖后不能降級為偏向鎖。這種鎖升級卻不能降級的策略,目的是為提高獲取鎖和釋放鎖的效率。

偏向鎖

HotSpot [1] 的作者經過研究發(fā)現(xiàn),大多數(shù)情況下,鎖不僅不存在多線程競爭,而且總是由同一線程多次獲得,為了讓線程獲得鎖的代價更低而引入了偏向鎖。當一個線程訪問同步塊并獲取鎖時,會在對象頭和棧幀中的鎖記錄里存儲鎖偏向的線程ID,以后該線程在進入和退出同步塊時不需要進行CAS操作來加鎖和解鎖,只需簡單地測試一下對象頭的Mark Word里是否存儲著指向當前線程的偏向鎖。如果測試成功,表示線程已經獲得了鎖。如果測試失敗,則需要再測試一下Mark Word中偏向鎖的標識是否設置成1(表示當前是偏向鎖):如果沒有設置,則使用CAS競爭鎖;如果設置了,則嘗試使用CAS將對象頭的偏向鎖指向當前線程。

偏向鎖的撤銷

偏向鎖使用了一種等到競爭出現(xiàn)才釋放鎖的機制,所以當其他線程嘗試競爭偏向鎖時,持有偏向鎖的線程才會釋放鎖。偏向鎖的撤銷,需要等待全局安全點(在這個時間點上沒有正在執(zhí)行的字節(jié)碼)。它會首先暫停擁有偏向鎖的線程,然后檢查持有偏向鎖的線程是否活著,如果線程不處于活動狀態(tài),則將對象頭設置成無鎖狀態(tài);如果線程仍然活著,擁有偏向鎖的棧會被執(zhí)行,遍歷偏向對象的鎖記錄,棧中的鎖記錄和對象頭的Mark Word要么重新偏向于其他線程,要么恢復到無鎖或者標記對象不適合作為偏向鎖,最后喚醒暫停的線程。圖2-1中的線程1演示了偏向鎖初始化的流程,線程2演示了偏向鎖撤銷的流程。

image.png
關閉偏向鎖

偏向鎖在Java 6和Java 7里是默認啟用的,但是它在應用程序啟動幾秒鐘之后才激活,如有必要可以使用JVM參數(shù)來關閉延遲:-XX:BiasedLockingStartupDelay=0。如果你確定應用程序里所有的鎖通常情況下處于競爭狀態(tài),可以通過JVM參數(shù)關閉偏向鎖:-XX:-UseBiasedLocking=false,那么程序默認會進入輕量級鎖狀態(tài)。

輕量級鎖

輕量級鎖加鎖

線程在執(zhí)行同步塊之前,JVM會先在當前線程的棧楨中創(chuàng)建用于存儲鎖記錄的空間,并將對象頭中的Mark Word復制到鎖記錄中,官方稱為Displaced Mark Word。然后線程嘗試使用CAS將對象頭中的Mark Word替換為指向鎖記錄的指針。如果成功,當前線程獲得鎖,如果失敗,表示其他線程競爭鎖,當前線程便嘗試使用自旋來獲取鎖。

輕量級鎖解鎖

輕量級解鎖時,會使用原子的CAS操作將Displaced Mark Word替換回到對象頭,如果成功,則表示沒有競爭發(fā)生。如果失敗,表示當前鎖存在競爭,鎖就會膨脹成重量級鎖。圖2-2是兩個線程同時爭奪鎖,導致鎖膨脹的流程圖。

image.png

因為自旋會消耗CPU,為了避免無用的自旋(比如獲得鎖的線程被阻塞住了),一旦鎖升級成重量級鎖,就不會再恢復到輕量級鎖狀態(tài)。當鎖處于這個狀態(tài)下,其他線程試圖獲取鎖時,都會被阻塞住,當持有鎖的線程釋放鎖之后會喚醒這些線程,被喚醒的線程就會進行新一輪的奪鎖之爭。

鎖的優(yōu)缺點對比

image.png

本文參考書籍
《Java并發(fā)編程的藝術》
《實戰(zhàn)Java高并發(fā)程序設計》

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • 一、摘要 ?在《深入剖析Java關鍵字之volatile》的文章中,我們知道volatile關鍵字能夠解決多線程編...
    SunnyMore閱讀 2,416評論 1 13
  • Java8張圖 11、字符串不變性 12、equals()方法、hashCode()方法的區(qū)別 13、...
    Miley_MOJIE閱讀 3,912評論 0 11
  • 實現(xiàn)原理 Synchronized可以保證一個在多線程運行中,同一時刻只有一個方法或者代碼塊被執(zhí)行,它還可以保證共...
    Java技術天地閱讀 2,346評論 0 18
  • 我的手機一個游戲都沒有,我就是一個不喜歡玩游戲的人。 我好像和周圍的人融不入,來來來開黑,我說我不玩游戲之后他就轉...
    芹Danae閱讀 106評論 0 0
  • 我站在南風的田野 泥鰍,細沙,小橋流水人家 我在想象一季稻野 陽光,泥濘,斗笠草帽衣襟 我站在南風的田野 綠色是春...
    揀愛閱讀 266評論 2 3

友情鏈接更多精彩內容