併發編程之synchronized(二)------jvm對synchronized的優化

1、鎖的粗化

看以下代碼java

public class Test {
    StringBuffer stb = new StringBuffer();


    public void test1(){
        //jvm的優化,鎖的粗化
        stb.append("1");

        stb.append("2");

        stb.append("3");

        stb.append("4");
    }

首先咱們要清除StringBuffer是線程安全的,由於它在每個方法上都加了synchronized鎖,下圖是StringBuffer的源碼安全

 

 按照正常的理解synchronized是對當前對象加鎖,那麼咱們調用了四次append方法,那麼jvm是將這把對象鎖加了四次嗎?以下圖:多線程

 

 那這樣的化,jvm就須要加四次鎖,固然也要釋放四次鎖,頻繁加解鎖引發線程上下文的切換,很是消耗性能,因此jvm作了優化,只加一次鎖,叫作鎖的粗化,能夠理解爲將鎖的顆粒度放大app

 2、鎖的消除

如圖看下面代碼jvm

 public void test2(){
        //jvm的優化,JVM不會對同步塊進行加鎖
        synchronized (new Object()) {
            //僞代碼:不少邏輯
            //jvm是否會加鎖?
            //jvm會進行逃逸分析
        }
    }

這個地方加鎖等於沒有加鎖,由於每一個線程都會new object,你們都不會用同一把鎖,jvm分析優化後不會對這種代碼加鎖(逃逸分析),因此,咱們平時加鎖必定要注意,加鎖要加同一把鎖。性能

3、鎖的膨脹升級

一、鎖的升級

synchronized的鎖的狀態總共有四種,無鎖狀態、偏向鎖、輕量級鎖和重量級鎖。隨着鎖的競爭,鎖能夠從偏向鎖升級到輕量級鎖,再升級的重量級鎖,可是鎖的升級是單向的,也就是說只能從低到高升級,鎖狀態的升級不可逆。優化

 

 

 

JDK1.6版本以後對synchronized的實現進行了各類優化,如自旋鎖、偏向鎖和輕量級鎖 並默認開啓偏向鎖 開啓偏向鎖:-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0 關閉偏向鎖:-XX:-UseBiasedLocking。若是直接上來就是重量級鎖,那麼實在是太消耗資源了。spa

二、鎖的狀態記錄在哪裏

HotSpot虛擬機的對象頭包括兩部分信息,第一部分是「Mark Word」,用於存儲對象自身的運行時數據, 如哈希碼(HashCode)、GC分代年齡、鎖狀態標誌、線程持有的鎖、偏向線程ID、偏向時間戳等等,這部分數據的長度在32位和64位的虛擬機(暫 不考慮開啓壓縮指針的場景)中分別爲32個和64個Bits,官方稱它爲「Mark Word」。

 

 三、對鎖的理解:

(1)、偏向鎖,偏向鎖是Java 6以後加入的新鎖,它是一種針對加鎖操做的優化手段,通過研究發現,在大多數狀況下,鎖不只不存在多線程競爭,並且老是由同一線程屢次得到,所以爲了減小同一線程獲取鎖(會涉及到一些CAS操做,耗時)的代價而引入偏向鎖。偏向鎖的核心思想是,若是一個線程得到了鎖,那麼鎖就進入偏向模式,此時Mark Word 的結構也變爲偏向鎖結構,當這個線程再次請求鎖時,無需再作任何同操做,即獲取鎖的過程,這樣就省去了大量有關鎖申請的操做,從而也就提供程序的性能。因此,對於沒有鎖競爭的場合,偏向鎖有很好的優化效果,畢竟極有可能連續屢次是同一個線程申請相同的鎖。可是對於鎖競爭比較激烈的場合,偏向鎖就失效了,由於這樣場合極有可能每次申請鎖的線程都是不相同的,所以這種場合下不該該使用偏向鎖,不然會得不償失,須要注意的是,偏向鎖失敗後,並不會當即膨脹爲重量級鎖,而是先升級爲輕量級鎖。下面咱們接着瞭解輕量級鎖。
(2)輕量級鎖 、假若偏向鎖失敗,虛擬機並不會當即升級爲重量級鎖,它還會嘗試使用一種稱爲輕量級鎖的優化手段(1.6以後加入的),此時Mark Word 的結構也變爲輕量級鎖的結構。輕量級鎖可以提高程序性能的依據是「對絕大部分的鎖,在整個同步週期內都不存在競爭」,注意這是經驗數據。須要瞭解的是,輕量級鎖所適應的場景是線程交替執行同步塊的場合,若是存在同一時間訪問同一鎖的場合,就會致使輕量級鎖膨脹爲重量級鎖。
(3)自旋鎖輕量級鎖失敗後,虛擬機爲了不線程真實地在操做系統層面掛起,還會進行一項稱爲自旋鎖的優化手段。這是基於在大多數狀況下,線程持有鎖的時間都不會太長,若是直接掛起操做系統層面的線程可能會得不償失,畢竟操做系統實現線程之間的切換時須要從用戶態轉換到核心態,這個狀態之間的轉換須要相對比較長的時間,時間成本相對較高,所以自旋鎖會假設在不久未來,當前的線程能夠得到鎖,所以虛擬機會讓當前想要獲取鎖的線程作幾個空循環(這也是稱爲自旋的緣由),通常不會過久,多是50個循環或100循環,在通過若干次循環後,若是獲得鎖,就順利進入臨界區。若是還不能得到鎖,那就會將線程在操做系統層面掛起,這就是自旋鎖的優化方式,這種方式確實也是能夠提高效率的。最後沒辦法也就只能升級爲重量級鎖了。

四、鎖的膨脹升級過程

 

 

 

注意一下幾點:操作系統

線程1獲取輕量級鎖後會將Object Mark Word 複製本身的一份到本身的棧空間,而後在本身的棧空間開闢一個指針lockerecord 指向Object Mark Word,同時Object Mark Word也會指向lockerecord,當線程1執行完代碼塊釋放輕量級鎖以後,發現Object Mark Word不在指向本身,說明當前鎖已經改成重量級鎖,那麼它會喚醒阻塞隊列中全部線程從新競爭鎖。線程

總結:偏向鎖,輕量級鎖都是基於Object Mark Word的標記實現,java儘量避免使用重量級鎖。

相關文章
相關標籤/搜索