偏向鎖的【批量重偏向與批量撤銷】機制

前言

從網上看了不少批量重偏向與批量撤銷的介紹,但都只知其一;不知其二,本着鑽研的精神,使用數據進行分析批量重偏向與批量撤銷的工做機制。java

準備

首先,要先知道偏向鎖的偏向鎖機制,着重看下撤銷機制。
而後,要知道【批量重偏向與批量撤銷】的研究對象是Class,即鎖對象對應的Class,而非鎖對象自己。jvm

// 研究的是對象.class
synchronized(對象) {
}
// 並非說的這種,這種不考慮
synchronized(對象.class) {
}
如對機制不瞭解,能夠看下這個博客 http://www.javashuo.com/article/p-kqdfoqoq-gt.html

另外幾個jvm參數:spa

-XX:BiasedLockingBulkRebiasThreshold = 20   // 默認偏向鎖批量重偏向閾值
-XX:BiasedLockingBulkRevokeThreshold = 40   // 默認偏向鎖批量撤銷閾值
-XX:+UseBiasedLocking // 使用偏向鎖,jdk6以後默認開啓
-XX:BiasedLockingStartupDelay = 0 // 延遲偏向時間, 默認不爲0,意思爲jvm啓動多少ms之後開啓偏向鎖機制(此處設爲0,不延遲)

在這裏,我把博客中的圖拷過來,方便你們思考。
image.net

正文

咱們主要看下圖中的 「是否開啓重偏向?」 這個條件分支,來看下什麼是重偏向?線程

重偏向字面理解就是從新偏向,那什麼狀況下會從新偏向呢

批量重偏向

咱們首先要知道的是,鎖對象Class剛開始的狀況下,是沒有開啓重偏向的,意思就是 「是否開啓重偏向?」 這個分支剛開始的時候是一直走 「否」 的,即會一直撤銷偏向鎖 ,當達到BiasedLockingBulkRebiasThreshold(20)次數的時候,容許重偏向code

下面咱們看下代碼對象

public class TestBiasedLocking {
    public static void main(String[] args) throws DecoderException, InterruptedException {
        // 首先咱們建立一個list,來存放鎖對象
        List<TestBiasedLocking> list = new LinkedList<>();
        
        // 線程1
        new Thread(() -> {
            for (int i = 0; i < 50; i++) {
                TestBiasedLocking testBiasedLocking = new TestBiasedLocking();
                list.add(testBiasedLocking); // 新建鎖對象
                synchronized (testBiasedLocking) {
                    System.out.println("第" + (i + 1) + "次加鎖-線程1");
                    System.out.println(ClassLayout.parseInstance(testBiasedLocking).toPrintable()); // 打印對象頭信息
                }
            }
        }, "線程1").start();
        
        // 讓線程1跑一下子
        Thread.sleep(2000);
        
        // 線程2
        new Thread(() -> {
            for (int i = 0; i < 30; i++) {
                TestBiasedLocking testBiasedLocking = list.get(i);
                synchronized (testBiasedLocking) {
                    System.out.println("第" + (i + 1) + "次加鎖-線程2");
                    System.out.println(ClassLayout.parseInstance(testBiasedLocking).toPrintable()); // 打印對象頭信息
                }
            }
        }, "線程2").start();

        LockSupport.park();
    }
}

image.png

先解釋下是什麼意思,這是輸出了三次對象信息,第一行就是對象頭信息(分爲4組,每組8位,查看方式爲,組(左-右),位(右-左)),紅框內的最後三位就是最低的三位,即 偏向鎖(1bit)+鎖標誌(2bit),其餘研究下就明白了;
1)【線程1】1-50次加鎖都是101,即偏向鎖狀態;這個沒什麼問題,線程1首次加的鎖,而且沒有別的線程競爭,因此對象頭是偏向鎖狀態,對應的Thread Id爲線程1.
2)【線程2】1-19加的鎖都是輕量級鎖,即前19次進行了偏向鎖撤銷,第20次執行了重偏向,線程id指向線程2;
舉個不太形象的例子。好比你結婚了,你就屬於你老公的了,可是隔壁老王看上你了(線程2加鎖),這個時候你是不能和他在一塊兒的(不可重偏向),除非離婚(鎖消除,升級爲輕量級鎖,不可再結婚,偏向鎖不可逆);有一天,zf統計發現某個地區離婚太頻繁了,直接給他們一次換老公的機會(可重偏向),前提是這個機會只給老公不在家的(正在使用的鎖對象已經屬於正在使用的線程);
下面咱們把例子帶入那個數據中,當第19我的離婚後,zf直接給要離婚的人一次換人的機會,因此第20我的就直接把結婚證上的人名換成老王了(Tread Id直接指向線程2)

批量撤銷

後來zf發現,換過老公的這批人,離婚仍是那麼頻繁(重偏向過的鎖對象頻繁 撤銷偏向鎖),搞我呢?都玩完!這個地區都別給我結婚了之後(該類下的鎖對象都不支持偏向鎖了,正在運行的也撤銷)。

這就是批量撤銷的含義,咱們代碼來演示下blog

public class TestBiasedLocking {

    public static void main(String[] args) throws DecoderException, InterruptedException {
        // 首先咱們建立一個list,來存放鎖對象
        List<TestBiasedLocking> list = new LinkedList<>();

        // 線程1
        new Thread(() -> {
            for (int i = 0; i < 50; i++) {
                TestBiasedLocking testBiasedLocking = new TestBiasedLocking();
                list.add(testBiasedLocking); // 新建鎖對象
                synchronized (testBiasedLocking) {
                    System.out.println("第" + (i + 1) + "次加鎖-線程1");
                    System.out.println(ClassLayout.parseInstance(testBiasedLocking).toPrintable());
                }

            }
            LockSupport.park();
        }, "線程1").start();

        // 讓線程1跑一下子
        Thread.sleep(2000);

        // 線程2
        new Thread(() -> {
            for (int i = 0; i < 40; i++) {
                TestBiasedLocking testBiasedLocking = list.get(i);
                synchronized (testBiasedLocking) {
                        System.out.println("第" + (i + 1) + "次加鎖-線程2");
                        System.out.println(ClassLayout.parseInstance(testBiasedLocking).toPrintable());

                }
            }
            LockSupport.park();
        }, "線程2").start();

        // 讓線程2跑一下子
        Thread.sleep(2000);

        // 線程3
        new Thread(() -> {
            for (int i = 20; i < 40; i++) {
                TestBiasedLocking testBiasedLocking = list.get(i);
                synchronized (testBiasedLocking) {
                        System.out.println("第" + (i + 1) + "次加鎖-線程3");
                        System.out.println(ClassLayout.parseInstance(testBiasedLocking).toPrintable());
                }
            }
            LockSupport.park();
        }, "線程3").start();

        // 讓線程3跑一下子
        Thread.sleep(2000);
        System.out.println("新出生的妹子");
        System.out.println(ClassLayout.parseInstance(new TestBiasedLocking()).toPrintable());
        LockSupport.park();
        }
    }

image.png

下面咱們用通俗的語言來解釋下rem

1) 線程一、1-50都結婚了(有偏向的線程了)
2) 線程二、1-19都離婚了(鎖撤銷),zf看不行啊,直接結婚證更名吧,20-40都換老公了(Thread Id直接換了) // 到達BiasedLockingBulkRebiasThreshold次數
3) 線程三、20-40又離婚了(鎖撤銷),滾蛋,都玩完,誰都不能結婚,結婚的也都給我離婚 (設置爲不可偏向狀態,正在運行的鎖對象會被撤銷)// 到達BiasedLockingBulkRevokeThreshold次數
4) 新出生的妹子,生下來就不讓結婚。(new出來就是輕量級鎖)
二婚的人持續時間超過-XX:BiasedLockingDecayTime=25000ms的話,撤銷次數清爲0,從新計算。

最後

最後我來解釋下文章中出現的舉例get

怎麼判斷只有一次換老公的機會的(每一個對象是怎麼判斷只有重偏向的一次機會的?)
答:對象頭中偏向鎖有個Epoch,該對象對應的Class中也有這個字段,當可偏向的時候,Class中的Epoch+1,正在使用的鎖對象Epoch也會+1(老公在家,沒有機會換老公),判斷可重偏向的時候,Class.Epoch != 對象.Epoch,表明可重偏向。


歡迎指正。

相關文章
相關標籤/搜索