自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

不吃飯也要掌握的Synchronized鎖升級(jí)過程

開發(fā) 后端
在JDK1.6之前,Synchronized的性能一直沒有ReentrantLock性能高,主要是因?yàn)镾ynchronized涉及到用戶態(tài)和內(nèi)核態(tài)的切換,這個(gè)是在操作系統(tǒng)和硬件是非常消耗資源的。經(jīng)過不斷的統(tǒng)計(jì)分析,發(fā)現(xiàn)大部分時(shí)間一個(gè)鎖都是一個(gè)線程去獲取,如果只有一個(gè)線程來嘗試加鎖,就是重量級(jí)鎖,顯而浪費(fèi)資源。

一、前言

在面試題中經(jīng)常會(huì)有這么一道面試題,談一下synchronized鎖升級(jí)過程?

之前背了一些,很多文章也說了,到底怎么什么條件才會(huì)觸發(fā)升級(jí),一直不太明白。

實(shí)踐是檢驗(yàn)真理的唯一標(biāo)準(zhǔn),今天就和大家一起實(shí)踐一下,什么條件才會(huì)升級(jí)!

二、為什么會(huì)有鎖升級(jí)過程?

在實(shí)踐之前,我們先一步步的來了解!為什么要升級(jí)呢?

在JDK1.6之前,synchronized的性能一直沒有ReentrantLock性能高,主要是因?yàn)閟ynchronized涉及到用戶態(tài)和內(nèi)核態(tài)的切換,這個(gè)是在操作系統(tǒng)和硬件是非常消耗資源的。

經(jīng)過不斷的統(tǒng)計(jì)分析,發(fā)現(xiàn)大部分時(shí)間一個(gè)鎖都是一個(gè)線程去獲取,如果只有一個(gè)線程來嘗試加鎖,就是重量級(jí)鎖,顯而浪費(fèi)資源。

「總之,鎖的升級(jí)過程是為了提高多線程環(huán)境下的性能和吞吐量,減少同步操作的開銷,并盡量避免線程切換的開銷。Java虛擬機(jī)根據(jù)線程競爭的情況和鎖的使用情況自動(dòng)進(jìn)行鎖的升級(jí)和降級(jí),以優(yōu)化多線程程序的性能?!?/p>

此時(shí),就引入了很多鎖類型,下面我們來具體看看!

三、鎖分類

偏向鎖:偏向鎖是為了解決單線程訪問的場景,偏向鎖允許第一個(gè)訪問共享資源的線程獲得鎖,把線程id存到對(duì)象頭中,后續(xù)的訪問可以直接獲得鎖,而不需要競爭。

輕量級(jí)鎖:當(dāng)一個(gè)或多個(gè)線程嘗試獲取同一個(gè)鎖時(shí),偏向鎖會(huì)升級(jí)為輕量級(jí)鎖。輕量級(jí)鎖采用CAS(Compare and Swap)操作來減小鎖的競爭。采用自適應(yīng)自旋!

重量級(jí)鎖:操作系統(tǒng)的調(diào)度器會(huì)介入,將競爭鎖的線程掛起,直到鎖被釋放為止,重量級(jí)鎖的開銷相對(duì)較高。

「補(bǔ)充:」

「自適應(yīng)自旋的基本思想是根據(jù)鎖的爭用情況,決定線程是否應(yīng)該自旋等待,以及自旋等待的時(shí)間,一般情況為自旋10次?!?/p>

四、對(duì)象內(nèi)存結(jié)構(gòu)

我們在說鎖的升級(jí)過程之前,需要了解一下對(duì)象的內(nèi)存結(jié)構(gòu),因?yàn)樵阪i升級(jí)過程中會(huì)往對(duì)象頭上進(jìn)行填充信息!一個(gè)對(duì)象分為:對(duì)象頭、實(shí)例數(shù)據(jù)、對(duì)其填充位三部分組成。

我們本次主要用到對(duì)象頭,我們再看一下詳細(xì)的對(duì)象頭信息里有什么:

五、圖解鎖升級(jí)過程

先來一個(gè)簡圖:

下面引用百度上的一張?jiān)敿?xì)一點(diǎn)的圖:

我們來詳細(xì)的說一下鎖的升級(jí)過程,在每一個(gè)鎖切換時(shí)的條件是什么?

在JDK8時(shí),偏向鎖默認(rèn)是在程序啟動(dòng)后4s自動(dòng)開啟的,在JKD15之后默認(rèn)是不開啟的!

可以設(shè)置無延遲時(shí)間啟動(dòng):-XX:BiasedLockingStartupDelay=0也可以不啟動(dòng)偏向鎖:-XX:-UseBiasedLocking = false。

直接說有點(diǎn)不形象,我們下面結(jié)合代碼來實(shí)戰(zhàn),看一下具體情況!

六、實(shí)戰(zhàn)鎖升級(jí)過程

為了我們能夠查詢對(duì)象結(jié)構(gòu),我們需要引入jar幫助我們查看!

1、導(dǎo)入依賴

「注意」:不要使用高版本的,高版本不顯示2進(jìn)制,不好觀察!

<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.10</version>
</dependency>

2、實(shí)戰(zhàn)代碼和解析

我們來從序號(hào)1開始,上面也說了默認(rèn)4s后開啟偏向鎖,我們會(huì)發(fā)現(xiàn)序號(hào)1打印的對(duì)象頭序號(hào)為:001我們的對(duì)象大小為20,內(nèi)部幫我們補(bǔ)位來滿足是8的倍數(shù)。方便操作系統(tǒng)進(jìn)行尋址,不會(huì)有碎片組合!這個(gè)大家可以詳細(xì)搜一下,這里就一帶而過了哈!

此時(shí)我們睡眠6s,包裝偏向鎖開啟成功!

我們來到序號(hào)2,開啟了偏向鎖,我們發(fā)現(xiàn)對(duì)象頭序號(hào)為:101。

「節(jié)點(diǎn):從無鎖到偏向鎖切換的條件:JDK8中默認(rèn)4s后開啟,JDK15需要手動(dòng)開啟」。

來到序號(hào)3和4一起說吧,當(dāng)我們進(jìn)行synchronized加鎖時(shí),對(duì)象的頭信息中會(huì)記錄上當(dāng)前線程的id,下面再有加鎖的,直接判斷線程id是否一致,一致直接進(jìn)入代碼塊。不一致后面再說!我們發(fā)現(xiàn)在序號(hào)4時(shí),已經(jīng)出了代碼塊,在此查詢加鎖的對(duì)象,信息依舊在,不會(huì)進(jìn)行移除,這就是偏向,直到下一個(gè)線程把上一個(gè)替換掉!

代碼里循環(huán)了三次,對(duì)象都是一樣的!

「節(jié)點(diǎn):在只有一個(gè)線程訪問代碼塊的時(shí)候,對(duì)象中會(huì)記錄當(dāng)前線程id。」

「以上都是在一個(gè)線程來訪問的情況下」

來到序號(hào)5,我們新建了一個(gè)線程來進(jìn)行加鎖。此時(shí)會(huì)判斷當(dāng)前線程id和新線程id是否一致,不一致就會(huì)認(rèn)為有競爭關(guān)系,會(huì)立刻切換為輕量級(jí)鎖。對(duì)象頭序號(hào)為:00

「節(jié)點(diǎn):當(dāng)有兩個(gè)線程交替獲取鎖時(shí),不存在同時(shí)競爭獲取鎖時(shí)?!?/p>

序號(hào)6和7一起說,我們讓上面序號(hào)5這個(gè)線程獲取鎖后睡眠3s,持續(xù)獲得鎖。在開啟一個(gè)新的線程去競爭獲取鎖,此時(shí)先進(jìn)行自適應(yīng)CAS自旋,一般10次后一直沒辦法獲取鎖,判定為激烈競爭關(guān)系。變?yōu)橹亓考?jí)鎖,序號(hào)7線程會(huì)進(jìn)行放到阻塞隊(duì)列中。對(duì)象頭序號(hào)為:10。

經(jīng)過睡眠后,序號(hào)6在此獲取對(duì)象的信息時(shí),已經(jīng)變?yōu)橹亓考?jí)鎖!

「節(jié)點(diǎn):有兩個(gè)及其以上線程同時(shí)獲取鎖,且在自適應(yīng)自旋范圍內(nèi)沒有獲取到鎖」。

下面是代碼,大家可以在本地試一下!

/**
 * jvm默認(rèn)延時(shí)4s自動(dòng)開啟偏向鎖,
 * 可通過 -XX:BiasedLockingStartupDelay=0
 * 取消延時(shí)如果不要偏向鎖,可通過-XX:-UseBiasedLocking = false
 * @author wangzhenjun
 * @date 2023/10/18 14:42
 */
public class LockUp {

    @SneakyThrows
    public static void main(String[] args) {

        LockInfo lockInfo = new LockInfo();
        System.out.println("1.無狀態(tài):" + ClassLayout.parseInstance(lockInfo).toPrintable());

        Thread.sleep(6000);
        LockInfo lock = new LockInfo();
        System.out.println("2.已經(jīng)開啟了偏向鎖模式:" + ClassLayout.parseInstance(lock).toPrintable());

        for (int i = 0; i < 3; i++) {
            synchronized (lock) {
                System.out.println("3.偏向鎖模式下,加鎖狀態(tài):" + ClassLayout.parseInstance(lock).toPrintable());
            }
            System.out.println("4.鎖釋放了,加鎖狀態(tài):" + ClassLayout.parseInstance(lock).toPrintable());
        }

        new Thread(() -> {
            synchronized (lock) {
                System.out.println("5.輕量級(jí)鎖,加鎖狀態(tài):" + ClassLayout.parseInstance(lock).toPrintable());

                System.out.println("睡眠3s");
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("6.輕量級(jí)鎖=>重量級(jí)鎖,加鎖狀態(tài):" + ClassLayout.parseInstance(lock).toPrintable());
            }
        }).start();

        Thread.sleep(1000);

        new Thread(() -> {
            synchronized (lock) {
                System.out.println("重量級(jí)鎖,加鎖狀態(tài):" + ClassLayout.parseInstance(lock).toPrintable());
            }
        }).start();

    }
}

七、總結(jié)與拓展

經(jīng)過實(shí)戰(zhàn),我們知道了每一個(gè)的切換條件,可以在面試中好好地回答了。不至于面試官反問一下就不堅(jiān)定了!

關(guān)于切換到重量級(jí)鎖后,有興趣的話,可以下載openJDK源碼去看一下關(guān)于hotspot/src/share/vm/runtime/objectMonitor.cpp和hotspot/src/share/vm/runtime/objectMonitor.hpp。

源碼下載地址:https://github.com/openjdk/jdk8

objectMonitor.cpp:是 OpenJDK 中實(shí)現(xiàn) Java 同步機(jī)制的核心部分,它負(fù)責(zé)管理對(duì)象監(jiān)視器,確保多線程程序能夠正確協(xié)同工作,實(shí)現(xiàn)線程同步和等待/通知機(jī)制。

objectMonitor.hpp:主要用于定義對(duì)象監(jiān)視器的接口和數(shù)據(jù)結(jié)構(gòu),為實(shí)際的對(duì)象監(jiān)視器的實(shí)現(xiàn)提供了基礎(chǔ)。

責(zé)任編輯:姜華 來源: 小王博客基地
相關(guān)推薦

2022-03-08 08:44:13

偏向鎖Java內(nèi)置鎖

2024-06-27 08:55:41

2024-08-13 14:08:25

2021-03-31 10:05:26

偏向鎖輕量級(jí)鎖

2021-07-01 19:30:23

JVM內(nèi)部鎖線程

2021-07-06 13:32:55

JVM

2020-04-06 13:50:43

數(shù)據(jù)安全大數(shù)據(jù)5G

2024-04-19 08:05:26

鎖升級(jí)Java虛擬機(jī)

2022-07-04 08:01:01

鎖優(yōu)化Java虛擬機(jī)

2021-12-16 18:38:13

面試Synchronize

2020-05-07 08:07:57

synchronize線程

2011-11-28 12:31:20

JavaJVM

2021-08-03 07:40:46

Synchronize鎖膨脹性能

2021-02-07 07:40:31

Synchronize用法

2021-12-14 14:50:12

synchronizeJava

2009-09-25 16:43:44

Hibernate悲觀Hibernate樂觀

2009-12-16 10:57:16

Ruby文件鎖

2024-10-08 09:10:03

JDK通信并發(fā)

2010-07-26 15:17:46

SQL Server鎖

2024-08-28 08:00:00

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)