日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

面經(jīng)手冊 · 第15篇《碼農(nóng)會(huì)鎖,synchronized 解毒,剖析源碼深度分析!》

 小傅哥 2021-12-13

作者:小傅哥
博客:https://

沉淀、分享、成長,讓自己和他人都能有所收獲!😄

一、前言

感覺什么都不會(huì),從哪開始呀!

這是最近我總能被問到的問題,也確實(shí)是。一個(gè)初入編程職場的新人,或是一個(gè)想重新努力學(xué)習(xí)的老司機(jī),這也不會(huì),那也不會(huì),總會(huì)犯愁從哪開始。

講道理,畢竟 Java 涉及的知識太多了,要學(xué)應(yīng)該是學(xué)會(huì)學(xué)習(xí)的能力,而不是去背題、背答案,拾人牙慧是不會(huì)有太多收益的。

學(xué)習(xí)的過程要找對方法,遇到問題時(shí)最好能自己想想,你有哪些方式學(xué)會(huì)這些知識。是不感覺即使讓你去百度搜,你都不知道應(yīng)該拿哪個(gè)關(guān)鍵字搜!只能拿著問題直接找人問,這樣缺少思考,缺少大腦撞南墻的過程,其實(shí)最后也很難學(xué)會(huì)。

所以,你要學(xué)會(huì)的是自我學(xué)習(xí)的能力,之后是從哪開始都可以,重要的是開始和堅(jiān)持!

二、面試題

謝飛機(jī),小記,周末逛完奧特萊斯,回來就跑面試官家去了!

謝飛機(jī):duang、duang、duang,我來了!

面試官:來的還挺準(zhǔn)時(shí),洗洗手吃飯吧!

謝飛機(jī):嘿嘿…

面試官:你看我這塊魚豆腐,像不像 synchronized 鎖!

謝飛機(jī):啊!?

面試官:飛機(jī),正好問你。synchronized、volatile,有什么區(qū)別呀?

謝飛機(jī):嗯,volatile 保證可見性,synchronized 保證原子性!

面試官:那不用 volatile,只用 synchronized 修飾方式,能保證可見性嗎?

謝飛機(jī):這…,我沒驗(yàn)證過!

面試官:吃吧,吃吧!一會(huì)給你個(gè) synchronized 學(xué)習(xí)大綱,照著整理知識點(diǎn)!

三、synchronized 解毒

圖 15-0 面試官給謝飛機(jī)的,synchronized 學(xué)習(xí)大綱

1. 對象結(jié)構(gòu)

1.1 對象結(jié)構(gòu)介紹

圖 15-1 64位JVM對象結(jié)構(gòu)描述

HotSpot虛擬機(jī) markOop.cpp 中的 C++ 代碼注釋片段,描述了 64bits 下 mark-word 的存儲(chǔ)狀態(tài),也就是圖 15-1 的結(jié)構(gòu)示意。

這部分的源碼注釋如下:

64 bits:
--------
unused:25 hash:31 -->| unused:1   age:4    biased_lock:1 lock:2 (normal object)
JavaThread*:54 epoch:2 unused:1   age:4    biased_lock:1 lock:2 (biased object)
PromotedObject*:61 --------------------->| promo_bits:3 ----->| (CMS promoted object)
size:64 ----------------------------------------------------->| (CMS free block)

unused:25 hash:31 -->| cms_free:1 age:4    biased_lock:1 lock:2 (COOPs && normal object)
JavaThread*:54 epoch:2 cms_free:1 age:4    biased_lock:1 lock:2 (COOPs && biased object)
narrowOop:32 unused:24 cms_free:1 unused:4 promo_bits:3 ----->| (COOPs && CMS promoted object)
unused:21 size:35 -->| cms_free:1 unused:7 ------------------>| (COOPs && CMS free block)

源碼地址jdk8/hotspot/file/vm/oops/markOop.hpp

HotSpot虛擬機(jī)中,對象在內(nèi)存中存儲(chǔ)的布局可以分為三塊區(qū)域:對象頭(Header)實(shí)例數(shù)據(jù)(Instance Data)對齊填充(Padding)。

  • mark-word:對象標(biāo)記字段占4個(gè)字節(jié),用于存儲(chǔ)一些列的標(biāo)記位,比如:哈希值、輕量級鎖的標(biāo)記位,偏向鎖標(biāo)記位、分代年齡等。
  • Klass Pointer:Class對象的類型指針,Jdk1.8默認(rèn)開啟指針壓縮后為4字節(jié),關(guān)閉指針壓縮(-XX:-UseCompressedOops)后,長度為8字節(jié)。其指向的位置是對象對應(yīng)的Class對象(其對應(yīng)的元數(shù)據(jù)對象)的內(nèi)存地址。
  • 對象實(shí)際數(shù)據(jù):包括對象的所有成員變量,大小由各個(gè)成員變量決定,比如:byte占1個(gè)字節(jié)8比特位、int占4個(gè)字節(jié)32比特位。
  • 對齊:最后這段空間補(bǔ)全并非必須,僅僅為了起到占位符的作用。由于HotSpot虛擬機(jī)的內(nèi)存管理系統(tǒng)要求對象起始地址必須是8字節(jié)的整數(shù)倍,所以對象頭正好是8字節(jié)的倍數(shù)。因此當(dāng)對象實(shí)例數(shù)據(jù)部分沒有對齊的話,就需要通過對齊填充來補(bǔ)全。

另外,在mark-word鎖類型標(biāo)記中,無鎖,偏向鎖,輕量鎖,重量鎖,以及GC標(biāo)記,5種類中沒法用2比特標(biāo)記(2比特最終有4種組合00、01、10、11),所以無鎖、偏向鎖,前又占了一位偏向鎖標(biāo)記。最終:001為無鎖、101為偏向鎖。

1.2 驗(yàn)證對象結(jié)構(gòu)

為了可以更加直觀的看到對象結(jié)構(gòu),我們可以借助 openjdk 提供的 jol-core 進(jìn)行打印分析。

引入POM

<!-- https://mvnrepository.com/artifact/org.openjdk.jol/jol-cli -->
<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-cli</artifactId>
    <version>0.14</version>
</dependency>

測試代碼

public static void main(String[] args) {
    System.out.println(VM.current().details());
    Object obj = new Object();
    System.out.println(obj + " 十六進(jìn)制哈希:" + Integer.toHexString(obj.hashCode()));
    System.out.println(ClassLayout.parseInstance(obj).toPrintable());
}
1.2.1 指針壓縮開啟(默認(rèn))

運(yùn)行結(jié)果

# Running 64-bit HotSpot VM.
# Using compressed oop with 3-bit shift.
# Using compressed klass with 3-bit shift.
# Objects are 8 bytes aligned.
# Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
# Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]

java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

圖 15-2 指針壓縮開啟,對象頭布局

  • Object對象,總共占16字節(jié)
  • 對象頭占 12 個(gè)字節(jié),其中:mark-word 占 8 字節(jié)、Klass Point 占 4 字節(jié)
  • 最后 4 字節(jié),用于數(shù)據(jù)填充找齊
1.2.2 指針壓縮關(guān)閉

Run-->Edit Configurations->VM Options 配置參數(shù) -XX:-UseCompressedOops 關(guān)閉指針壓縮。

運(yùn)行結(jié)果

java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           01 12 0c 53 (00000001 00010010 00001100 01010011) (1393299969)
      4     4        (object header)                           02 00 00 00 (00000010 00000000 00000000 00000000) (2)
      8     4        (object header)                           00 1c b9 1b (00000000 00011100 10111001 00011011) (465116160)
     12     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

圖 15-3 指針壓縮關(guān)閉,對象頭布局

  • 關(guān)閉指針壓縮后,mark-word 還是占 8 字節(jié)不變。
  • 重點(diǎn)在類型指針 Klass Point 的變化,由原來的 4 字節(jié),現(xiàn)在擴(kuò)增到 8 字節(jié)。
1.2.3 對象頭哈希值存儲(chǔ)驗(yàn)證

接下來,我們調(diào)整下測試代碼,看下哈希值在對象頭中具體是怎么存放的。

測試代碼

public static void main(String[] args) {
    System.out.println(VM.current().details());
    Object obj = new Object();
    System.out.println(obj + " 十六進(jìn)制哈希:" + Integer.toHexString(obj.hashCode()));
    System.out.println(ClassLayout.parseInstance(obj).toPrintable());
}
  • 改動(dòng)不多,只是把哈希值和對象打印出來,方便我們驗(yàn)證對象頭關(guān)于哈希值的存放結(jié)果。

運(yùn)行結(jié)果

圖 15-3 對象頭哈希值存放

  • 如圖 15-3,對象的哈希值是16進(jìn)制的,0x2530c12
  • 在對象頭哈希值存放的結(jié)果上看,也有對應(yīng)的數(shù)值。只不過這個(gè)結(jié)果是倒過來的。

關(guān)于這個(gè)倒過來的問題是因?yàn)?#xff0c;大小端存儲(chǔ)導(dǎo)致;

  • Big-Endian:高位字節(jié)存放于內(nèi)存的低地址端,低位字節(jié)存放于內(nèi)存的高地址端
  • Little-Endian:低位字節(jié)存放于內(nèi)存的低地址端,高位字節(jié)存放于內(nèi)存的高地址端

mark-word結(jié)構(gòu)

圖 15-5 無鎖狀態(tài),64位虛擬機(jī)mark-word結(jié)構(gòu)

如圖 15-5 最右側(cè)的 3 Bit(1 Bit標(biāo)識偏向鎖,2 Bit描述鎖的類型)是跟鎖類型和GC標(biāo)記相關(guān)的,而 synchronized 的鎖優(yōu)化升級膨脹就是修改的這三位上的標(biāo)識,來區(qū)分不同的鎖類型。從而采取不同的策略來提升性能。

1.3 Monitor 對象

在HotSpot虛擬機(jī)中,monitor是由C++中ObjectMonitor實(shí)現(xiàn)。

synchronized 的運(yùn)行機(jī)制,就是當(dāng) JVM 監(jiān)測到對象在不同的競爭狀況時(shí),會(huì)自動(dòng)切換到適合的鎖實(shí)現(xiàn),這種切換就是鎖的升級、降級。

那么三種不同的 Monitor 實(shí)現(xiàn),也就是常說的三種不同的鎖:偏斜鎖(Biased Locking)、輕量級鎖和重量級鎖。當(dāng)一個(gè) Monitor 被某個(gè)線程持有后,它便處于鎖定狀態(tài)。

Monitor 主要數(shù)據(jù)結(jié)構(gòu)如下

// initialize the monitor, exception the semaphore, all other fields
// are simple integers or pointers
ObjectMonitor() {
    _header       = NULL;
    _count        = 0;       // 記錄個(gè)數(shù)
    _waiters      = 0,
    _recursions   = 0;       // 線程重入次數(shù)
    _object       = NULL;    // 存儲(chǔ) Monitor 對象
    _owner        = NULL;    // 持有當(dāng)前線程的 owner
    _WaitSet      = NULL;    // 處于wait狀態(tài)的線程,會(huì)被加入到 _WaitSet
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ;   // 單向列表
    FreeNext      = NULL ;
    _EntryList    = NULL ;   // 處于等待鎖block狀態(tài)的線程,會(huì)被加入到該列表
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
    _previous_owner_tid = 0;
}

源碼地址jdk8/hotspot/file/vm/runtime/objectMonitor.hpp

  • ObjectMonitor,有兩個(gè)隊(duì)列:_WaitSet、_EntryList,用來保存 ObjectWaiter 對象列表。
  • _owner,獲取 Monitor 對象的線程進(jìn)入 _owner 區(qū)時(shí), _count + 1。如果線程調(diào)用了 wait() 方法,此時(shí)會(huì)釋放 Monitor 對象, _owner 恢復(fù)為空, _count - 1。同時(shí)該等待線程進(jìn)入 _WaitSet 中,等待被喚醒。

鎖🔒執(zhí)行效果如下

圖 15-06,鎖🔒執(zhí)行效果

如圖 15-06,每個(gè) Java 對象頭中都包括 Monitor 對象(存儲(chǔ)的指針的指向),synchronized 也就是通過這一種方式獲取鎖,也就解釋了為什么 synchronized() 括號里放任何對象都能獲得鎖🔒!

2. synchronized 特性

2.1 原子性

原子性是指一個(gè)操作是不可中斷的,要么全部執(zhí)行成功要么全部執(zhí)行失敗。

案例代碼

private static volatile int counter = 0;
public static void main(String[] args) throws InterruptedException {
    for (int i = 0; i < 10; i++) {
        Thread thread = new Thread(() -> {
            for (int i1 = 0; i1 < 10000; i1++) {
                add();
            }
        });
        thread.start();
    }
    // 等10個(gè)線程運(yùn)行完畢
    Thread.sleep(1000);
    System.out.println(counter);
}
public static void add() {
    counter++;
}

這段代碼開啟了 10 個(gè)線程來累加 counter,按照預(yù)期結(jié)果應(yīng)該是 100000。但實(shí)際運(yùn)行會(huì)發(fā)現(xiàn),counter 值每次運(yùn)行都小于 10000,這是因?yàn)?volatile 并不能保證原子性,所以最后的結(jié)果不會(huì)是10000。

修改方法 add(),添加 synchronized:

public static void add() {
    synchronized (AtomicityTest.class) {
        counter++;
    }
}

這回測試結(jié)果就是:100000 了!

因?yàn)?synchronized 可以保證統(tǒng)一時(shí)間只有一個(gè)線程能拿到鎖,進(jìn)入到代碼塊執(zhí)行。

反編譯查看指令碼

javap -v -p AtomicityTest

public static void add();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=0
         0: ldc           #12                 // class org/itstack/interview/AtomicityTest
         2: dup
         3: astore_0
         4: monitorenter
         5: getstatic     #10                 // Field counter:I
         8: iconst_1
         9: iadd
        10: putstatic     #10                 // Field counter:I
        13: aload_0
        14: monitorexit
        15: goto          23
        18: astore_1
        19: aload_0
        20: monitorexit
        21: aload_1
        22: athrow
        23: return
      Exception table:

同步方法

ACC_SYNCHRONIZED 這是一個(gè)同步標(biāo)識,對應(yīng)的16進(jìn)制值是 0x0020

這10個(gè)線程進(jìn)入這個(gè)方法時(shí),都會(huì)判斷是否有此標(biāo)識,然后開始競爭 Monitor 對象。

同步代碼

  • monitorenter,在判斷擁有同步標(biāo)識 ACC_SYNCHRONIZED 搶先進(jìn)入此方法的線程會(huì)優(yōu)先擁有 Monitor 的 owner ,此時(shí)計(jì)數(shù)器 +1。
  • monitorexit,當(dāng)執(zhí)行完退出后,計(jì)數(shù)器 -1,歸 0 后被其他進(jìn)入的線程獲得。

2.2 可見性

在上一章節(jié) volatile 篇中,我們知道它保證變量對所有線程的可見性。最終的效果就是在添加 volatile 的屬性變量時(shí),線程A修改值后,線程B使用此變量可以做出相應(yīng)的反應(yīng),比如 while(!變量) 退出。

那么,synchronized 具備可見性嗎,我們做給例子。

public static boolean sign = false;
public static void main(String[] args) {
    Thread Thread01 = new Thread(() -> {
        int i = 0;
        while (!sign) {
            i++;
            add(i);
        }
    });
    Thread Thread02 = new Thread(() -> {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException ignore) {
        }
        sign = true;
        logger.info("vt.sign = true  while (!sign)")
    });
    Thread01.start();
    Thread02.start();
}

public static int add(int i) {
    return i + 1;
}

這是兩個(gè)線程操作一個(gè)變量的例子,因?yàn)榫€程間對變量 sign 的不可見性,線程 Thread01 中的 while (!sign) 會(huì)一直執(zhí)行,不會(huì)隨著線程 Thread02 修改 sign = true 而退出循環(huán)。

現(xiàn)在我們給方法 add 添加 synchronized 關(guān)鍵字修飾,如下:

public static synchronized int add(int i) {
    return i + 1;
}

添加后運(yùn)行結(jié)果

23:55:33.849 [Thread-1] INFO  org.itstack.interview.VisibilityTest - vt.sign = true  while (!sign)

Process finished with exit code 0

可以看到當(dāng)線程 Thread02 改變變量 sign = true 后,線程 Thread01 立即退出了循環(huán)。

注意:不要在方法中添加 System.out.println() ,因?yàn)檫@個(gè)方法中含有 synchronized 會(huì)影響測試結(jié)果!

那么為什么添加 synchronized 也能保證變量的可見性呢?

因?yàn)?#xff1a;

  1. 線程解鎖前,必須把共享變量的最新值刷新到主內(nèi)存中。
  2. 線程加鎖前,將清空工作內(nèi)存中共享變量的值,從而使用共享變量時(shí)需要從主內(nèi)存中重新讀取最新的值。
  3. volatile 的可見性都是通過內(nèi)存屏障(Memnory Barrier)來實(shí)現(xiàn)的。
  4. synchronized 靠操作系統(tǒng)內(nèi)核互斥鎖實(shí)現(xiàn),相當(dāng)于 JMM 中的 lock、unlock。退出代碼塊時(shí)刷新變量到主內(nèi)存。

2.3 有序性

as-if-serial,保證不管編譯器和處理器為了性能優(yōu)化會(huì)如何進(jìn)行指令重排序,都需要保證單線程下的運(yùn)行結(jié)果的正確性。也就是常說的:如果在本線程內(nèi)觀察,所有的操作都是有序的;如果在一個(gè)線程觀察另一個(gè)線程,所有的操作都是無序的。

這里有一段雙重檢驗(yàn)鎖(Double-checked Locking)的經(jīng)典案例:

public class Singleton {
    private Singleton() {
    }

    private volatile static Singleton instance;

    public Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }

}

為什么,synchronized 也有可見性的特點(diǎn),還需要 volatile 關(guān)鍵字?

因?yàn)?#xff0c;synchronized 的有序性,不是 volatile 的防止指令重排序。

那如果不加 volatile 關(guān)鍵字可能導(dǎo)致的結(jié)果,就是第一個(gè)線程在初始化初始化對象,設(shè)置 instance 指向內(nèi)存地址時(shí)。第二個(gè)線程進(jìn)入時(shí),有指令重排。在判斷 if (instance == null) 時(shí)就會(huì)有出錯(cuò)的可能,因?yàn)檫@會(huì)可能 instance 可能還沒有初始化成功。

2.4 可重入性

synchronized 是可重入鎖,也就是說,允許一個(gè)線程二次請求自己持有對象鎖的臨界資源,這種情況稱為可重入鎖🔒。

那么我們就寫一個(gè)例子,來證明這樣的情況。

public class ReentryTest extends A{

    public static void main(String[] args) {
        ReentryTest reentry = new ReentryTest();
        reentry.doA();
    }

    public synchronized void doA() {
        System.out.println("子類方法:ReentryTest.doA() ThreadId:" + Thread.currentThread().getId());
        doB();
    }

    private synchronized void doB() {
        super.doA();
        System.out.println("子類方法:ReentryTest.doB() ThreadId:" + Thread.currentThread().getId());
    }

}


class A {
    public synchronized void doA() {
        System.out.println("父類方法:A.doA() ThreadId:" + Thread.currentThread().getId());
    }
}

測試結(jié)果

子類方法:ReentryTest.doA() ThreadId:1
父類方法:A.doA() ThreadId:1
子類方法:ReentryTest.doB() ThreadId:1

Process finished with exit code 0

這段單例代碼是遞歸調(diào)用含有 synchronized 鎖的方法,從運(yùn)行正常的測試結(jié)果看,并沒有發(fā)生死鎖。所有可以證明 synchronized 是可重入鎖。

synchronized鎖對象的時(shí)候有個(gè)計(jì)數(shù)器,他會(huì)記錄下線程獲取鎖的次數(shù),在執(zhí)行完對應(yīng)的代碼塊之后,計(jì)數(shù)器就會(huì)-1,直到計(jì)數(shù)器清零,就釋放鎖了。

之所以,是可以重入。是因?yàn)?synchronized 鎖對象有個(gè)計(jì)數(shù)器,會(huì)隨著線程獲取鎖后 +1 計(jì)數(shù),當(dāng)線程執(zhí)行完畢后 -1,直到清零釋放鎖。

3. 鎖升級過程

關(guān)于 synchronized 鎖🔒升級有一張非常完整的圖,可以參考:

圖 15-7 synchronized 鎖升級過程

synchronized 鎖有四種交替升級的狀態(tài):無鎖、偏向鎖、輕量級鎖和重量級,這幾個(gè)狀態(tài)隨著競爭情況逐漸升級。

3.1 偏向鎖

synchronizer源碼:/src/share/vm/runtime/synchronizer.cpp

// NOTE: must use heavy weight monitor to handle jni monitor exit
void ObjectSynchronizer::jni_exit(oop obj, Thread* THREAD) {
  TEVENT (jni_exit) ;
  if (UseBiasedLocking) {
    Handle h_obj(THREAD, obj);
    BiasedLocking::revoke_and_rebias(h_obj, false, THREAD);
    obj = h_obj();
  }
  assert(!obj->mark()->has_bias_pattern(), "biases should be revoked by now");

  ObjectMonitor* monitor = ObjectSynchronizer::inflate(THREAD, obj);
  // If this thread has locked the object, exit the monitor.  Note:  can't use
  // monitor->check(CHECK); must exit even if an exception is pending.
  if (monitor->check(THREAD)) {
     monitor->exit(true, THREAD);
  }
}
  • UseBiasedLocking 是一個(gè)偏向鎖檢查,1.6之后是默認(rèn)開啟的,1.5中是關(guān)閉的,需要手動(dòng)開啟參數(shù)是 XX:-UseBiasedLocking=false

偏斜鎖會(huì)延緩 JIT 預(yù)熱進(jìn)程,所以很多性能測試中會(huì)顯式地關(guān)閉偏斜鎖,偏斜鎖并不適合所有應(yīng)用場景,撤銷操作(revoke)是比較重的行為,只有當(dāng)存在較多不會(huì)真正競爭的 synchronized 塊兒時(shí),才能體現(xiàn)出明顯改善。

3.2 輕量級鎖

當(dāng)鎖是偏向鎖的時(shí)候,被另一個(gè)線程所訪問,偏向鎖就會(huì)升級為輕量級鎖,其他線程會(huì)通過自旋的形式嘗試獲取鎖,不會(huì)阻塞,提高性能。

在代碼進(jìn)入同步塊的時(shí)候,如果同步對象鎖狀態(tài)為無鎖狀態(tài)(鎖標(biāo)志位為“01”狀態(tài),是否為偏向鎖為“0”),JVM虛擬機(jī)首先將在當(dāng)前線程的棧幀中建立一個(gè)名為鎖記錄(Lock Record)的空間,用于存儲(chǔ)鎖對象目前的Mark Word的拷貝,官方稱之為 Displaced Mark Word。

3.3 自旋鎖

自旋鎖是指嘗試獲取鎖的線程不會(huì)立即阻塞,而是采用循環(huán)的方式去嘗試獲取鎖,這樣的好處是減少線程上下文切換的消耗,缺點(diǎn)是循環(huán)會(huì)消耗CPU。

自旋鎖的默認(rèn)大小是10次,可以調(diào)整:-XX:PreBlockSpin

如果自旋n次失敗了,就會(huì)升級為重量級的鎖。重量級的鎖,在 1.3 Monitor 對象中已經(jīng)介紹。

3.4 鎖會(huì)降級嗎?

之前一直了解到 Java 不會(huì)進(jìn)行鎖降級,但最近整理了大量的資料發(fā)現(xiàn)鎖降級確實(shí)是會(huì)發(fā)生。

When safepoints are used?

Below are few reasons for HotSpot JVM to initiate a safepoint:
Garbage collection pauses
Code deoptimization
Flushing code cache
Class redefinition (e.g. hot swap or instrumentation)
Biased lock revocation
Various debug operation (e.g. deadlock check or stacktrace dump)

Biased lock revocation,當(dāng) JVM 進(jìn)入安全點(diǎn) SafePoint的時(shí)候,會(huì)檢查是否有閑置的 Monitor,然后試圖進(jìn)行降級。

四、總結(jié)

  • 本章關(guān)于 synchronized 鎖涉及到了較多的C++源碼分析學(xué)習(xí),源碼地址:https://github.com/JetBrains/jdk8u_hotspot
  • 關(guān)于鎖的細(xì)節(jié)挖掘除了本文提到的還有很多知識點(diǎn)可以繼續(xù)學(xué)習(xí),可以結(jié)合 ifeve、并發(fā)編程、深入理解JVM虛擬機(jī),等系列知識整理。
  • 學(xué)習(xí)過程中結(jié)合C++源代碼中關(guān)于鎖的實(shí)現(xiàn),更容易理解可能原本晦澀難懂的概念。在結(jié)合實(shí)際的案例驗(yàn)證,會(huì)容易接受這部分知識。
  • 好了,這篇就寫到這里了,如果有觀點(diǎn)和文章不準(zhǔn)確的表達(dá)歡迎留言,互相學(xué)習(xí),互相掃盲,互相進(jìn)步。

五、傅詩一手

  • 會(huì)所🏢,里的碼農(nóng)會(huì)鎖。
  • 擁擠🤼?♂?,就需加價(jià)升級。
  • 項(xiàng)目🤯,按摩對象頭皮。
  • 效果🤨,可見原子有序。

六、系列推薦

  • 認(rèn)知自己的技術(shù)棧盲區(qū)
  • 握草,你竟然在代碼里下毒!
  • 一次代碼評審,差點(diǎn)過不了試用期!
  • ThreadLocal 技術(shù)棧深度學(xué)習(xí)
  • HashMap核心知識,擾動(dòng)函數(shù)、負(fù)載因子、擴(kuò)容鏈表拆分

    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多