Synchronized原理分析

1. 何時須要用Synchronized
Synchronized主要做用是在多個線程操做共享數據的時候,保證對共享數據訪問的線程安全性。好比兩個線程對於i這個共享變量同時作i++遞增操做,那麼這個時候對於i這個值來講就存在一個不肯定性,也就是說理論上i的值應該是2,可是也多是1。而致使這個問題的緣由是線程並行執行i++操做並非原子的,存在線程安全問題。因此一般來講解決辦法是經過加鎖來實現線程的串行執行,而synchronized就是java中鎖的實現的關鍵字。
synchronized在併發編程中是一個很是重要的角色,在JDK1.6以前,它是一個重量級鎖的角色,可是在JDK1.6以後對synchronized作了優化,優化之後性能有了較大的提高
2.Synchronized的使用
synchronized有三種使用方法,這三種使用方法分別對應三種不一樣的做用域,代碼以下:
①修飾普通同步方法
將synchronized修飾在普通同步方法,那麼該鎖的做用域是在當前實例對象範圍內,也就是說對於 SyncDemosd=newSyncDemo();這一個實例對象sd來講,多個線程訪問access方法會有鎖的限制。若是access已經有線程持有了鎖,那這個線程會獨佔鎖,直到鎖釋放完畢以前,其餘線程都會被阻塞。
public SyncDemo{java

  1. Object lock =new Object();
  2. //形式1
  3. public synchronized void access(){
  4. //
  5. }
  6. //形式2,做用域等同於形式1
  7. public void access1(){
  8. synchronized(lock){
  9. //
  10. }
  11. }

②修飾靜態同步方法
修飾靜態同步方法或者靜態對象、類,那麼這個鎖的做用範圍是類級別。舉個簡單的例子,{SyncDemo sd=SyncDemo();SyncDemo sd2=new SyncDemo();} 兩個不一樣的實例sd和sd2, 若是sd這個實例訪問access方法而且成功持有了鎖,那麼sd2這個對象若是一樣來訪問access方法,那麼它必需要等待sd這個對象的鎖釋放之後,sd2這個對象的線程才能訪問該方法,這就是類鎖;也就是說類鎖就至關於全局鎖的概念,做用範圍是類級別。編程

  1. public SyncDemo{
  2. static Object lock=new Object();
  3. //形式1
  4. public synchronized static void access(){
  5. //
  6. }
  7. //形式2等同於形式1
  8. public void access1(){
  9. synchronized(lock){
  10. //
  11. }
  12. }
  13. //形式3等同於前面兩種
  14. public void access2(){
  15. synchronzied(SyncDemo.class){
  16. //
  17. }
  18. }
  19. }

③.同步方法塊
同步方法塊,是範圍最小的鎖,鎖的是synchronized括號裏面配置的對象。這種鎖在實際工做中使用得比較頻繁,畢竟鎖的做用範圍越大,那麼對性能的影響就越嚴重。安全

  1. public SyncDemo{
  2. Object lock=new Object();
  3. public void access(){
  4. //do something
  5. synchronized(lock){
  6. //
  7. }
  8. }
  9. }

3.Synchronized的實現原理分析多線程

synchronized實現的鎖是存儲在Java對象頭裏,什麼是對象頭呢?在Hotspot虛擬機中,對象在內存中的存儲佈局,能夠分爲三個區域:對象頭(Header)、實例數據(Instance Data)、對齊填充(Padding)
   當咱們在Java代碼中,使用new建立一個對象實例的時候,(hotspot虛擬機)JVM層面實際上會建立一個 instanceOopDesc對象。instanceOopDesc的定義在Hotspot源碼中的 instanceOop.hpp文件中,另外,arrayOopDesc的定義對應 arrayOop.hpp

clipboard.png
從instanceOopDesc代碼中能夠看到 instanceOopDesc繼承自oopDesc,oopDesc的定義載Hotspot源碼中的 oop.hpp文件中。併發

clipboard.png
在普通實例對象中,oopDesc的定義包含兩個成員,分別是 _mark和 _metadata,其中_mark表示對象標記、屬於markOop類型,也就是Mark World,它記錄了對象和鎖有關的信。_metadata表示類元信息,類元信息存儲的是對象指向它的類元數據(Klass)的首地址,其中Klass表示普通指針、 _compressed_klass表示壓縮類指針。oop

Mark Word
前面說的普通對象的對象頭由兩部分組成,分別是markOop以及類元信息,markOop官方稱爲Mark Word 。在Hotspot中,markOop的定義在 markOop.hpp文件中,代碼以下佈局

clipboard.png
Mark word記錄了對象和鎖有關的信息,當某個對象被synchronized關鍵字當成同步鎖時,那麼圍繞這個鎖的一系列操做都和Mark word有關係。Mark Word在32位虛擬機的長度是32bit、在64位虛擬機的長度是64bit。 Mark Word裏面存儲的數據會隨着鎖標誌位的變化而變化。
鎖標誌位的表示意義
1.鎖標識 lock=00 表示輕量級鎖
2.鎖標識 lock=10 表示重量級鎖
3.偏向鎖標識 biased_lock=1表示偏向鎖
4.偏向鎖標識 biased_lock=0且鎖標識=01表示無鎖狀態
4.鎖的升級
前面提到了鎖的幾個概念,偏向鎖、輕量級鎖、重量級鎖。在JDK1.6以前,synchronized是一個重量級鎖,性能比較差。從JDK1.6開始,爲了減小得到鎖和釋放鎖帶來的性能消耗,synchronized進行了優化,引入了 偏向鎖和 輕量級鎖的概念。因此從JDK1.6開始,鎖一共會有四種狀態,鎖的狀態根據競爭激烈程度從低到高分別是:無鎖狀態->偏向鎖狀態->輕量級鎖狀態->重量級鎖狀態。這幾個狀態會隨着鎖競爭的狀況逐步升級。爲了提升得到鎖和釋放鎖的效率,鎖能夠升級可是不能降級。
偏向鎖
在大多數的狀況下,鎖不只不存在多線程的競爭,並且老是由同一個線程得到。所以爲了讓線程得到鎖的代價更低引入了偏向鎖的概念。偏向鎖的意思是若是一個線程得到了一個偏向鎖,若是在接下來的一段時間中沒有其餘線程來競爭鎖,那麼持有偏向鎖的線程再次進入或者退出同一個同步代碼塊,不須要再次進行搶佔鎖和釋放鎖的操做。偏向鎖能夠經過 -XX:+UseBiasedLocking開啓或者關閉。
偏向鎖的獲取
偏向鎖的獲取過程很是簡單,當一個線程訪問同步塊獲取鎖時,會在對象頭和棧幀中的鎖記錄裏存儲偏向鎖的線程ID,表示哪一個線程得到了偏向鎖,結合前面分析的Mark Word來分析一下偏向鎖的獲取邏輯
1首先獲取目標對象的Mark Word,根據鎖的標識爲和epoch去判斷當前是否處於可偏向的狀態
2若是爲可偏向狀態,則經過CAS操做將本身的線程ID寫入到MarkWord,若是CAS操做成功,則表示當前線程成功獲取到偏向鎖,繼續執行同步代碼塊
3若是是已偏向狀態,先檢測MarkWord中存儲的threadID和當前訪問的線程的threadID是否相等,若是相等,表示當前線程已經得到了偏向鎖,則不須要再得到鎖直接執行同步代碼;若是不相等,則證實當前鎖偏向於其餘線程,須要撤銷偏向鎖。
偏向鎖的撤銷
當其餘線程嘗試競爭偏向鎖時,持有偏向鎖的線程纔會釋放偏向鎖,撤銷偏向鎖的過程須要等待一個全局安全點(全部工做線程都中止字節碼的執行)。
1首先,暫停擁有偏向鎖的線程,而後檢查偏向鎖的線程是否爲存活狀態
2若是線程已經死了,直接把對象頭設置爲無鎖狀態
3若是還活着,當達到全局安全點時得到偏向鎖的線程會被掛起,接着偏向鎖升級爲輕量級鎖,而後喚醒被阻塞在全局安全點的線程繼續往下執行同步代碼
輕量級鎖
前面咱們知道,當存在超過一個線程在競爭同一個同步代碼塊時,會發生偏向鎖的撤銷。偏向鎖撤銷之後對象會可能會處於兩種狀態
1.一種是不可偏向的無鎖狀態,簡單來講就是已經得到偏向鎖的線程已經退出了同步代碼塊,那麼這個時候會撤銷偏向鎖,並升級爲輕量級鎖
2.一種是不可偏向的已鎖狀態,簡單來講就是已經得到偏向鎖的線程正在執行同步代碼塊,那麼這個時候會升級到輕量級鎖而且被原持有鎖的線程得到鎖
輕量級鎖加鎖
1.JVM會先在當前線程的棧幀中建立用於存儲鎖記錄的空間(LockRecord)
2.將對象頭中的Mark Word複製到鎖記錄中,稱爲Displaced Mark Word.
3.線程嘗試使用CAS將對象頭中的Mark Word替換爲指向鎖記錄的指針
4.若是替換成功,表示當前線程得到輕量級鎖,若是失敗,表示存在其餘線程競爭鎖,那麼當前線程會嘗試使用CAS來獲取鎖,當自旋超過指定次數(能夠自定義)時仍然沒法得到鎖,此時鎖會膨脹升級爲重量級鎖
輕量鎖解鎖
1.嘗試CAS操做將所記錄中的Mark Word替換回到對象頭中
2.若是成功,表示沒有競爭發生
3.若是失敗,表示當前鎖存在競爭,鎖會膨脹成重量級鎖
一旦鎖升級成重量級鎖,就不會再恢復到輕量級鎖狀態。當鎖處於重量級鎖狀態,其餘線程嘗試獲取鎖時,都會被阻塞,也就是 BLOCKED狀態。當持有鎖的線程釋放鎖以後會喚醒這些現場,被喚醒以後的線程會進行新一輪的競爭
重量級鎖
重量級鎖依賴對象內部的monitor鎖來實現,而monitor又依賴操做系統的MutexLock(互斥鎖),假設Mutex變量的值爲1,表示互斥鎖空閒,這個時候某個線程調用lock能夠得到鎖,而Mutex的值爲0表示互斥鎖已經被其餘線程得到,其餘線程調用lock只能掛起等待。
爲何重量級鎖的開銷比較大呢?緣由是當系統檢查到是重量級鎖以後,會把等待想要獲取鎖的線程阻塞,被阻塞的線程不會消耗CPU,可是阻塞或者喚醒一個線程,都須要經過操做系統來實現,也就是至關於從用戶態轉化到內核態,而轉化狀態是須要消耗時間的。性能

相關文章
相關標籤/搜索