Synchronized加鎖、鎖升級和java對象內存結構

首先了解一下JMM中定義的內存操做:java

一個線程操做數據時候都是從主內存(堆內存)讀取到本身工做內存(線程私有的數據區域)中再進行操做。對於硬件內存來講,並無工做內存和主內存的區分,這都是java內存模型劃分出來的,它只是一種抽象的概念,是一組規則,並非實際存在的。Java內存模型中定義了八種同步操做安全

1.lock(鎖定):做用於主內存的變量,把一個變量標記爲一條線程獨佔狀態多線程

2.unlock(解鎖):做用於主內存的變量,把一個處於鎖定狀態的變量釋放出來,釋放後的變量才能夠被其餘線程鎖定jvm

3.read(讀取):做用於主內存的變量,把一個變量值從主內存傳輸到線程的工做內存中,以便隨後的load動做使用 性能

4.load(載入):做用於工做內存的變量,它把read操做從主內存中獲得的變量值放入工做內存的變量副本中優化

5.use(使用):做用於工做內存的變量,把工做內存中的一個變量值傳遞給執行引擎spa

6.assign(賦值):做用於工做內存的變量,它把一個從執行引擎接收到的值賦給工做內存的變量操作系統

7.store(存儲):做用於工做內存的變量,把工做內存中的一個變量的值傳送到主內存中,以便隨後的write的操做 插件

8.write(寫入):做用於工做內存的變量,它把store操做從工做內存中的一個變量的值傳送到主內存的變量中線程

若是要把一個變量從主內存中複製到工做內存中,就須要按順序地執行readload操做, 若是把變量從工做內存中同步到主內存中,就須要按順序地執行storewrite操做。但Java內存模型只要求上述操做必須按順序執行,而沒有保證必須是連續執行。

同步規則:

  1. 不容許一個線程無緣由地(沒有發生過任何assign操做)把數據從工做內存同步回主內存 中
  2. 一個新的變量只能在主內存中誕生,不容許在工做內存中直接使用一個未被初始化(load 或者assign)的變量。即就是對一個變量實施usestore操做以前,必須先自行assignload 操做。
  3. 一個變量在同一時刻只容許一條線程對其進行lock操做,但lock操做能夠被同一線程重複 執行屢次,屢次執行lock後,只有執行相同次數的unlock操做,變量纔會被解鎖。lockunlock必須成對出現。
  4. 若是對一個變量執行lock操做,將會清空工做內存中此變量的值,在執行引擎使用這個變 量以前須要從新執行loadassign操做初始化變量的值。
  5. 若是一個變量事先沒有被lock操做鎖定,則不容許對它執行unlock操做;也不容許去 unlock一個被其餘線程鎖定的變量。

對一個變量執行unlock操做以前,必須先把此變量同步到主內存中(執行storewrite操做)

 

Synchronized

synchronizedjvm內置的同步鎖,它是隱式鎖,不須要咱們本身手動釋放鎖。

每個java對象中都有一個內部對象Monitorsynchronized就是經過內部對象Monitor(監視器鎖)實現,基於進入與退出Monitor對象實現方法與代碼塊同步,監視器鎖的實現依賴底層操做系統的Mutex lock(互斥鎖)實現,它是一個重量級鎖性能較低jdk1.6以後進行了優化)。

當咱們在代碼中使用了synchronized以後,能夠在字節碼文件看到MONITORENTERMONITOREXITIdea中安裝了ByteCode Viewer插件就能夠查看字節碼,選中編譯完的class文件

 

 

 

 

java虛擬機中ObjectMonitor的定義:(虛擬機C++代碼片斷)

 

 

加鎖的過程:

 

 

 

 Monitor.EnterMonitor.Exit就是做用在JMM中定義的內存操做中的lockunlock上面。而後從上面的同步規則中能夠知道一個變量在同一時刻只容許一條線程對其進行lock操做lock操做的時候會清空工做內存,從新去主內存load最新的數據。Unlock操做則會執行storewrite操做將工做內存中的數據寫回主內存。這也就是爲何咱們用了Synchronized關鍵字以後就可以實現線程安全。

 

Java對象內存結構:

對象在內存中存儲的結構由三部分組成:對象頭主要是一些標記信息MarkWord,好比hashcode,鎖狀態這些;實例數據就是真實的數據;對齊填充要求對象大小8字節的整數倍,若是不是就填充補齊。

 

 MarkWord鎖狀態標記就在這裏面,以32位jvm爲例,64位也是這些東西,只是佔的大小不同

 

無鎖狀態:前25位記錄的是hashcode,後四位是對象分代年齡,而後是不是偏向鎖標記

偏向鎖狀態:前23位是偏向的線程ID

輕量級鎖:前30位指向線程棧中鎖記錄的指針

重量級鎖:前30位指向重量級鎖Monitor的指針

 

 

JVM內置鎖優化升級

JDK1.6版本以後對synchronized的實現進行了各類優化,自旋鎖、偏向鎖和輕量級鎖

並默認開啓偏向鎖

開啓偏向鎖:-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0

關閉偏向鎖:-XX:-UseBiasedLocking

偏向鎖

  偏向鎖是Java 6以後加入的新鎖,它是一種針對加鎖操做的優化手段,通過研究發現,在大多數狀況下,鎖不只不存在多線程競爭,並且老是由同一線程屢次得到,所以爲了減小同一線程獲取鎖(會涉及到一些CAS操做,耗時)的代價而引入偏向鎖。偏向鎖的核心思想是,若是一個線程得到了鎖,那麼鎖就進入偏向模式,此時Mark Word 的結構也變爲偏向鎖結構,當這個線程再次請求鎖時,無需 再作任何同步操做,即獲取鎖的過程,這樣就省去了大量有關鎖申請的操做,從 而也就提供程序的性能。因此,對於沒有鎖競爭的場合,偏向鎖有很好的優化效 果,畢竟極有可能連續屢次是同一個線程申請相同的鎖。可是對於鎖競爭比較激 烈的場合,偏向鎖就失效了,由於這樣場合極有可能每次申請鎖的線程都是不相 同的,所以這種場合下不該該使用偏向鎖,不然會得不償失,須要注意的是,偏向鎖失敗後,並不會當即膨脹爲重量級鎖,而是先升級爲輕量級鎖。

輕量級鎖

  假若偏向鎖失敗,虛擬機並不會當即升級爲重量級鎖,它還會嘗試使用一種 稱爲輕量級鎖的優化手段(1.6以後加入的),此時Mark Word 的結構也變爲輕量 級鎖的結構。輕量級鎖可以提高程序性能的依據是「對絕大部分的鎖,在整個同 步週期內都不存在競爭」,注意這是經驗數據。須要瞭解的是,輕量級鎖所適應 的場景是線程交替執行同步塊的場合,若是存在同一時間訪問同一鎖的場合,就會致使輕量級鎖膨脹爲重量級鎖。這個時候也就是上面的Monitor.EnterMonitor.Exit鎖的升級過程是不可逆的。

自旋鎖

  虛擬機爲了不線程真實地在操做系統層面掛起,會進行一項稱爲自旋鎖的優化手段。它是一個過渡,每一次升級以前先進行自旋,好比經過必定的自旋以後發現仍是偏向鎖鎖的場景那麼就不進行鎖的升級。這是基於在大多數狀況下,線程持有鎖的時間都不會太長,若是直接掛起操做系統層面的線程可能會得不償失,畢竟操做系統實 現線程之間的切換時須要從用戶態轉換到核心態,這個狀態之間的轉換須要相對 比較長的時間,時間成本相對較高,所以自旋鎖會假設在不久未來,當前的線程 能夠得到鎖,所以虛擬機會讓當前想要獲取鎖的線程作幾個空循環(這也是稱爲 自旋的緣由),通常不會過久,多是50個循環或100循環,在通過若干次循環後,若是獲得鎖,就順利進入臨界區。若是還不能得到鎖,那就會將線程在操做 系統層面掛起,這就是自旋鎖的優化方式,這種方式確實也是能夠提高效率的。

整個過程以下圖

相關文章
相關標籤/搜索