偏向鎖、輕量級鎖、重量級鎖、自旋鎖原理講解

一、簡介

 在講解這些鎖概念之前,我們要明確的是這些鎖不等同於Java API中的ReentratLock這種鎖,這些鎖是概念上的,是JDK1.6中爲了對synchronized同步關鍵字進行優化而產生的的鎖機制。這些鎖的啓動和關閉策略可以通過設定JVM啓動參數來設置,當然在一般情況下,使用JVM默認的策略就可以了。

二、Java對象頭中的Mark Word

HotSpot中,Java的對象內存模型分爲三部分,分別爲對象頭、實例數據和對齊填充。而對象頭中分爲兩部分,一部分是“Mark Word”(存儲對象自身的運行時數據,32bit或64bit,可複用);另一部分是指向它的類的元數據指針。

因爲synchronized是Java對象的內置鎖,所以其優化策略(即偏向鎖等)的信息都包含在Mark Word中,讓我們先看一下Mark Word的結構。


         (圖片來自網絡)

其中最重要的是“鎖標誌位”和“是否偏向鎖”,鎖標誌位代表了當前對象內置鎖的狀態,不同的鎖狀態下Mark Word存儲的信息是不同的,因此稱爲可複用。

三、偏向鎖

 通俗的講,偏向鎖就是在運行過程中,對象的鎖偏向某個線程。即在開啓偏向鎖機制的情況下,某個線程獲得鎖,當該線程下次再想要獲得鎖時,不需要再獲得鎖(即忽略synchronized關鍵詞),直接就可以執行同步代碼,比較適合競爭較少的情況。

偏向鎖的獲取流程:

 (1)查看Mark Word中偏向鎖的標識以及鎖標誌位,若是否偏向鎖爲1且鎖標誌位爲01,則該鎖爲可偏向狀態。

 (2)若爲可偏向狀態,則測試Mark Word中的線程ID是否與當前線程相同,若相同,則直接執行同步代碼,否則進入下一步。

 (3)當前線程通過CAS操作競爭鎖,若競爭成功,則將Mark Word中線程ID設置爲當前線程ID,然後執行同步代碼,若競爭失敗,進入下一步。

 (4)當前線程通過CAS競爭鎖失敗的情況下,說明有競爭。當到達全局安全點時之前獲得偏向鎖的線程被掛起,偏向鎖升級爲輕量級鎖,然後被阻塞在安全點的線程繼續往下執行同步代碼。

偏向鎖的釋放流程:

 偏向鎖只有遇到其他線程嘗試競爭偏向鎖時,持有偏向鎖狀態的線程纔會釋放鎖,線程不會主動去釋放偏向鎖。偏向鎖的撤銷需要等待全局安全點(即沒有字節碼正在執行),它會暫停擁有偏向鎖的線程,撤銷後偏向鎖恢復到未鎖定狀態或輕量級鎖狀態。

下面看一個實驗:

/**
 * @author Lee
 * @date 2018/2/8
 * 開啓偏向鎖參數:-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0  耗時4000ms左右
 * 警用偏向鎖參數:-XX:-UseBiasedLocking  耗時1900ms左右
 */
public class VectorTest {

    public static void main(String[] args) {
        long time1 = System.currentTimeMillis();
        Vector<Integer> vector = new Vector<Integer>();
        for (int i = 0; i < 100000000; i++){
            vector.add(100);//add是synchronized操作
        }
        System.out.println(System.currentTimeMillis() - time1);
    }
}

 因爲Vector是同步容器,其add操作是有synchronized修飾的,在JVM參數中設置開啓偏向鎖或禁用偏向鎖時,其耗時相差很大,說明偏向鎖對synchronized關鍵詞的優化在單個線程中或競爭較少的線程中是很成功的。但是在多線程競爭十分頻繁的情況下,偏向鎖不僅不能提高效率,反而會因爲不斷地重新設置偏向線程ID等其他消耗而降低效率。

四、輕量級鎖

輕量級鎖不是用來替代傳統的重量級鎖的,而是在沒有多線程競爭的情況下,使用輕量級鎖能夠減少性能消耗,但是當多個線程同時競爭鎖時,輕量級鎖會膨脹爲重量級鎖。

輕量級鎖的加鎖過程:

(1)當線程執行代碼進入同步塊時,若Mark Word爲無鎖狀態,虛擬機先在當前線程的棧幀中建立一個名爲Lock Record的空間,用於存儲當前對象的Mark Word的拷貝,官方稱之爲“Dispalced Mark Word”,此時狀態如下圖:


(2)複製對象頭中的Mark Word到鎖記錄中。

(3)複製成功後,虛擬機將用CAS操作將對象的Mark Word更新爲執行Lock  Record的指針,並將Lock Record裏的owner指針指向對象的Mark Word。如果更新成功,則執行4,否則執行5。;

(4)如果更新成功,則這個線程擁有了這個鎖,並將鎖標誌設爲00,表示處於輕量級鎖狀態,此時狀態圖:


(5)如果更新失敗,虛擬機會檢查對象的Mark Word是否指向當前線程的棧幀,如果是則說明當前線程已經擁有這個鎖,可進入執行同步代碼。否則說明多個線程競爭,輕量級鎖就會膨脹爲重量級鎖,Mark Word中存儲重量級鎖(互斥鎖)的指針,後面等待鎖的線程也要進入阻塞狀態。

五、重量級鎖

即當有其他線程佔用鎖時,當前線程會進入阻塞狀態。

六、自旋鎖

 在自旋狀態下,當一個線程A嘗試進入同步代碼塊,但是當前的鎖已經被線程B佔有時,線程A不進入阻塞狀態,而是不停的空轉,等待線程B釋放鎖。如果鎖的線程能在很短時間內釋放資源,那麼等待競爭鎖的線程就不需要做內核態和用戶態之間的切換進入阻塞狀態,只需自旋,等持有鎖的線程釋放後即可立即獲取鎖,避免了用戶線程和內核的切換消耗。

自旋等待最大時間:線程自旋會消耗cpu,若自旋太久,則會讓cpu做太多無用功,因此要設置自旋等待最大時間。

優點:開啓自旋鎖後能減少線程的阻塞,在對於鎖的競爭不激烈且佔用鎖時間很短的代碼塊來說,能提升很大的性能,在這種情況下自旋的消耗小於線程阻塞掛起的消耗。

缺點:在線程競爭鎖激烈,或持有鎖的線程需要長時間執行同步代碼塊的情況下,使用自旋會使得cpu做的無用功太多。

JDK1.6中,設置參數 -XX:+UseSpinning開啓。

JDK1.7後,由JVM自動控制。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章