0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

synchronized的原理與四種用法介紹

冬至子 ? 來源:并發(fā)編程之美 ? 作者:妙啊 ? 2023-06-09 16:13 ? 次閱讀

介紹

JDK提供的鎖分兩種,一種是JVM實(shí)現(xiàn)的synchronized,是java的關(guān)鍵字,因此在這個(gè)關(guān)鍵字作用對(duì)象的范圍內(nèi)都是可以保證原子性的,主要是依賴特殊的CPU指令。另一種是JDK提供的代碼層面的鎖Lock。

一、synchronized的四種用法

1. 修飾代碼塊

大括號(hào)括起來的代碼,稱同步語句塊,作用范圍是大括號(hào),作用對(duì)象是調(diào)用代碼塊的對(duì)象。

public void test1(int j) {
    synchronized (this) {
        for (int i = 0; i < 10; i++) {
            log.info("test1 {} - {}", j, i);
        }
    }
}

測(cè)試代碼:

public static void main(String[] args) {
    SynchronizedExample1 example1 = new SynchronizedExample1();
    SynchronizedExample1 example2 = new SynchronizedExample1();
    ExecutorService executorService = Executors.newCachedThreadPool();
    executorService.execute(() - > {
        example1.test1(1);
    });
    executorService.execute(() - > {
        example2.test1(2);
    });
}

測(cè)試結(jié)果:

圖片

  • 可以看到test1方法的參數(shù)1和參數(shù)2是交替執(zhí)行。

2. 修飾方法

被修飾的方法稱為同步方法,作用范圍是整個(gè)方法,作用于調(diào)用對(duì)象。

public synchronized void test2(int j) {
    for (int i = 0; i < 10; i++) {
        log.info("test2 {} - {}", j, i);
    }
}

測(cè)試代碼:

public static void main(String[] args) {
    SynchronizedExample1 example1 = new SynchronizedExample1();
    SynchronizedExample1 example2 = new SynchronizedExample1();
    ExecutorService executorService = Executors.newCachedThreadPool();
    executorService.execute(() - > {
        example1.test2(1);
    });
    executorService.execute(() - > {
        example2.test2(2);
    });
}

測(cè)試結(jié)果:

圖片

  • 可以看到test2方法的參數(shù)1和參數(shù)2是交替執(zhí)行。

3. 修飾靜態(tài)方法

作用范圍是整個(gè)方法,作用于所有對(duì)象。

public static synchronized void test3(int j) {
    for (int i = 0; i < 10; i++) {
        log.info("test3 {} - {}", j, i);
    }
}

測(cè)試代碼:

public static void main(String[] args) {
    SynchronizedExample1 example1 = new SynchronizedExample1();
    SynchronizedExample1 example2 = new SynchronizedExample1();
    ExecutorService executorService = Executors.newCachedThreadPool();
    executorService.execute(() - > {
        example1.test3(1);
    });
    executorService.execute(() - > {
        example2.test3(2);
    });
}

測(cè)試結(jié)果:

圖片

  • 可以看到test3方法的參數(shù)1和參數(shù)2是1執(zhí)行完才執(zhí)行的2。

4. 修飾類

作用范圍是synchronized后面括號(hào)括起來的部分,作用于所有對(duì)象。

public static void test4(int j) {
    synchronized (SynchronizedExample2.class) {
        for (int i = 0; i < 10; i++) {
            log.info("test4 {} - {}", j, i);
        }
    }
}

測(cè)試代碼:

public static void main(String[] args) {
    SynchronizedExample1 example1 = new SynchronizedExample1();
    SynchronizedExample1 example2 = new SynchronizedExample1();
    ExecutorService executorService = Executors.newCachedThreadPool();
    executorService.execute(() - > {
        example1.test4(1);
    });
    executorService.execute(() - > {
        example2.test4(2);
    });
}

測(cè)試結(jié)果:

圖片

  • 可以看到test4方法的參數(shù)1和參數(shù)2是1執(zhí)行完才執(zhí)行的2。

二、synchronized的原理

在Java語言中存在兩種內(nèi)建的synchronized語法:synchronized語句、synchronized方法:

  • synchronized語句:當(dāng)源代碼被編譯成字節(jié)碼的時(shí)候,會(huì)在同步塊的入口位置和退出位置分別插入monitorenter和monitorexit字節(jié)碼指令。
  • synchronized方法:在Class文件的方法表中將該方法的access_flags字段中的synchronized標(biāo)志位置1

synchronized語句

如上示例,test1和test4使用的就是synchronized語句。使用Javap -c命令反編譯test1代碼,如下:

圖片

在Java虛擬機(jī)的specification中,有關(guān)于monitorenter和monitorexit字節(jié)碼指令的詳細(xì)描述:

monitorenter

每個(gè)對(duì)象都有一個(gè)鎖,也就是監(jiān)視器(monitor)。 Monitor可以理解為一個(gè)同步工具或一種同步機(jī)制,通常被描述為一個(gè)對(duì)象。 每一個(gè)Java對(duì)象就有一把看不見的鎖,稱為內(nèi)部鎖或者M(jìn)onitor鎖。

當(dāng)monitor被占有時(shí)就表示它被鎖定。線程執(zhí)行monitorenter指令時(shí)嘗試獲取對(duì)象所對(duì)應(yīng)的monitor的所有權(quán),過程如下:

  • 如果monitor的進(jìn)入數(shù)為0,則該線程進(jìn)入monitor,然后將進(jìn)入數(shù)設(shè)置為1,該線程即為monitor的所有者。
  • 如果線程已經(jīng)擁有了該monitor,只是重新進(jìn)入,則進(jìn)入monitor的進(jìn)入數(shù)加1。
  • 如果其他線程已經(jīng)占用了monitor,則該線程進(jìn)入阻塞狀態(tài),直到monitor的進(jìn)入數(shù)為0,再重新嘗試獲取monitor的所有權(quán)。

monitorexit

執(zhí)行monitorexit的線程必須是相應(yīng)的monitor的所有者。指令執(zhí)行時(shí),monitor的進(jìn)入數(shù)減1,如果減1后進(jìn)入數(shù)為0,那線程退出monitor,不再是這個(gè)monitor的所有者。其他被這個(gè)monitor阻塞的線程可以嘗試去獲取這個(gè)monitor的所有權(quán)。

synchronized方法

如上示例,test2和test3使用的就是synchronized方法。synchronized方法加鎖的方式是在Class文件的方法表中將該方法的access_flags字段中的synchronized標(biāo)志位置1。如下:

圖片

  • 訪問標(biāo)志的第11位即為加鎖標(biāo)記位。

三、synchronized的優(yōu)化

synchronized在操作同步資源之前需要給同步資源先加鎖,這把鎖就是存在Java對(duì)象頭里,而Java對(duì)象頭又是什么呢?

Java對(duì)象頭

以Hotspot虛擬機(jī)為例,Hotspot的對(duì)象頭主要包括兩部分?jǐn)?shù)據(jù):Mark Word(標(biāo)記字段)、Klass Pointer(類型指針)。

Mark Word

默認(rèn)存儲(chǔ)對(duì)象的HashCode,分代年齡和鎖標(biāo)志位信息。這些信息都是與對(duì)象自身定義無關(guān)的數(shù)據(jù),所以Mark Word被設(shè)計(jì)成一個(gè)非固定的數(shù)據(jù)結(jié)構(gòu)以便在極小的空間內(nèi)存存儲(chǔ)盡量多的數(shù)據(jù)。它會(huì)根據(jù)對(duì)象的狀態(tài)復(fù)用自己的存儲(chǔ)空間,也就是說在運(yùn)行期間Mark Word里存儲(chǔ)的數(shù)據(jù)會(huì)隨著鎖標(biāo)志位的變化而變化。

Klass Point

對(duì)象指向它的類元數(shù)據(jù)的指針,虛擬機(jī)通過這個(gè)指針來確定這個(gè)對(duì)象是哪個(gè)類的實(shí)例。

在JDK1.6及其之前的版本中monitorenter和monitorexit字節(jié)碼依賴于底層的操作系統(tǒng)的Mutex Lock來實(shí)現(xiàn)的,但是由于使用Mutex Lock需要將當(dāng)前線程掛起并從用戶態(tài)切換到內(nèi)核態(tài)來執(zhí)行,這種切換的代價(jià)是非常昂貴的。然而在現(xiàn)實(shí)中的大部分情況下,同步方法是運(yùn)行在單線程環(huán)境(無鎖競(jìng)爭(zhēng)環(huán)境)。如果每次都調(diào)用Mutex Lock將嚴(yán)重的影響程序的性能。因此在JDK6中為了減少獲得鎖和釋放鎖帶來的性能消耗,引入了偏向鎖輕量級(jí)鎖 。所以目前鎖一共有4種狀態(tài),級(jí)別從低到高依次是:無鎖、偏向鎖、輕量級(jí)鎖和重量級(jí)鎖。鎖狀態(tài)只能升級(jí)不能降級(jí)。如下:

圖片

無鎖

  • 無鎖沒有對(duì)資源進(jìn)行鎖定,所有的線程都能訪問并修改同一個(gè)資源,但同時(shí)只有一個(gè)線程能修改成功。
  • 無鎖的特點(diǎn)就是修改操作在循環(huán)內(nèi)進(jìn)行,線程會(huì)不斷的嘗試修改共享資源。如果沒有沖突就修改成功并退出,否則就會(huì)繼續(xù)循環(huán)嘗試。如果有多個(gè)線程修改同一個(gè)值,必定會(huì)有一個(gè)線程能修改成功,而其他修改失敗的線程會(huì)不斷重試直到修改成功。

偏向鎖

  • 偏向鎖是指一段同步代碼一直被一個(gè)線程所訪問,那么該線程會(huì)自動(dòng)獲取鎖,降低獲取鎖的代價(jià)。
  • 引入偏向鎖是為了在無多線程競(jìng)爭(zhēng)的情況下盡量減少不必要的輕量級(jí)鎖執(zhí)行路徑,其目標(biāo)就是在只有一個(gè)線程執(zhí)行同步代碼塊時(shí)能夠提高性能。
  • 當(dāng)一個(gè)線程訪問同步代碼塊并獲取鎖時(shí),會(huì)在Mark Word里存儲(chǔ)鎖偏向的線程ID。在線程進(jìn)入和退出同步塊時(shí)不再通過CAS操作來加鎖和解鎖,而是檢測(cè)Mark Word里是否存儲(chǔ)著指向當(dāng)前線程的偏向鎖。
  • 偏向鎖只有遇到其他線程嘗試競(jìng)爭(zhēng)偏向鎖時(shí),持有偏向鎖的線程才會(huì)釋放鎖,線程不會(huì)主動(dòng)釋放偏向鎖。偏向鎖的撤銷,需要等待全局安全點(diǎn)(在這個(gè)時(shí)間點(diǎn)上沒有字節(jié)碼正在執(zhí)行),它會(huì)首先暫停擁有偏向鎖的線程,判斷鎖對(duì)象是否處于被鎖定狀態(tài)。撤銷偏向鎖后恢復(fù)到無鎖或輕量級(jí)鎖的狀態(tài)。
  • 偏向鎖在JDK 6及以后的JVM里是默認(rèn)啟用的。可以通過JVM參數(shù)關(guān)閉偏向鎖: -XX:-UseBiasedLocking=false ,關(guān)閉之后程序默認(rèn)會(huì)進(jìn)入輕量級(jí)鎖狀態(tài)。

輕量級(jí)鎖

  • 是指當(dāng)鎖是偏向鎖的時(shí)候,被另外的線程所訪問,偏向鎖就會(huì)升級(jí)為輕量級(jí)鎖,其他線程會(huì)通過自旋的形式嘗試獲取鎖,不會(huì)阻塞,從而提高性能。
  • 在代碼進(jìn)入同步塊的時(shí)候,如果同步對(duì)象鎖狀態(tài)為無鎖狀態(tài)(鎖標(biāo)志位為01狀態(tài),是否為偏向鎖為0),虛擬機(jī)首先將在當(dāng)前線程的棧幀中建立一個(gè)名為鎖記錄(Lock Record)的空間,用于存儲(chǔ)鎖對(duì)象目前的Mark Word的拷貝,然后拷貝對(duì)象頭中的Mark Word復(fù)制到鎖記錄中。
  • 拷貝成功后,虛擬機(jī)將使用CAS操作嘗試將對(duì)象的Mark Word更新為指向Lock Record的指針,并將Lock Record里的owner指針指向?qū)ο蟮腗ark Word。
  • 如果這個(gè)更新動(dòng)作成功了,那么這個(gè)線程就擁有了該對(duì)象的鎖,并且對(duì)象Mark Word的鎖標(biāo)志位設(shè)置為00,表示此對(duì)象處于輕量級(jí)鎖定狀態(tài)。
  • 如果輕量級(jí)鎖的更新操作失敗了,虛擬機(jī)首先會(huì)檢查對(duì)象的Mark Word是否指向當(dāng)前線程的棧幀,如果是就說明當(dāng)前線程已經(jīng)擁有了這個(gè)對(duì)象的鎖,那就可以直接進(jìn)入同步塊繼續(xù)執(zhí)行,否則說明多個(gè)線程競(jìng)爭(zhēng)鎖。

重量級(jí)鎖

  • 若當(dāng)前只有一個(gè)等待線程,則該線程通過自旋進(jìn)行等待。但是當(dāng)自旋超過一定的次數(shù),或者一個(gè)線程在持有鎖,一個(gè)在自旋,又有第三個(gè)來訪時(shí),輕量級(jí)鎖升級(jí)為重量級(jí)鎖。
  • 升級(jí)為重量級(jí)鎖時(shí),鎖標(biāo)志的狀態(tài)值變?yōu)?0,此時(shí)Mark Word中存儲(chǔ)的是指向重量級(jí)鎖的指針,此時(shí)等待鎖的線程都會(huì)進(jìn)入阻塞狀態(tài)。

綜上,偏向鎖通過對(duì)比Mark Word解決加鎖問題,避免執(zhí)行CAS操作。而輕量級(jí)鎖是通過用CAS操作和自旋來解決加鎖問題,避免線程阻塞和喚醒而影響性能。重量級(jí)鎖是將除了擁有鎖的線程以外的線程都阻塞。

四、synchronized存在的問題

1.性能損耗

  • 雖然在JDK 1.6中對(duì)synchronized做了很多優(yōu)化,如如適應(yīng)性自旋、鎖消除、鎖粗化、輕量級(jí)鎖和偏向鎖等,但畢竟還是一種鎖。
  • 所以,無論是使用同步方法還是同步代碼塊,在同步操作之前還是要進(jìn)行加鎖,同步操作之后需要進(jìn)行解鎖,這個(gè)加鎖、解鎖的過程是要有性能損耗的。

2. 阻塞

  • synchronize實(shí)現(xiàn)的鎖本質(zhì)上是一種阻塞鎖,多個(gè)線程要排隊(duì)訪問同一個(gè)共享對(duì)象。
聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • JAVA語言
    +關(guān)注

    關(guān)注

    0

    文章

    138

    瀏覽量

    20026
  • JVM
    JVM
    +關(guān)注

    關(guān)注

    0

    文章

    155

    瀏覽量

    12168
  • 虛擬機(jī)
    +關(guān)注

    關(guān)注

    1

    文章

    888

    瀏覽量

    27812
  • CAS
    CAS
    +關(guān)注

    關(guān)注

    0

    文章

    34

    瀏覽量

    15160
收藏 人收藏

    評(píng)論

    相關(guān)推薦

    四種模擬輸入信號(hào)的保護(hù)電路實(shí)現(xiàn)方法

    本文介紹四種模擬輸入信號(hào)的保護(hù)電路的實(shí)現(xiàn)方法。
    發(fā)表于 03-28 09:55 ?1037次閱讀

    FPGA 設(shè)計(jì)的四種常用思想與技巧

    FPGA 設(shè)計(jì)的四種常用思想與技巧FPGA設(shè)計(jì)的四種常用思想與技巧 討論的四種常用FPGA/CPLD設(shè)計(jì)思想與技巧:乒乓操作、串并轉(zhuǎn)換、流水線操作、數(shù)據(jù)接口同步化,都是FPGA/CPLD 邏輯設(shè)計(jì)
    發(fā)表于 08-11 10:30

    四種無線充電技術(shù)簡(jiǎn)單原理

    詳細(xì)介紹了電場(chǎng)耦合 電磁感應(yīng) 磁共振無線電波 這四種方式
    發(fā)表于 07-28 11:12

    四種不同供電模式的LED拓?fù)?b class='flag-5'>介紹

    本文中,小編將為大家介紹四種在LED供電當(dāng)中經(jīng)常使用的四種拓?fù)浣Y(jié)構(gòu)。感興趣的朋友快來看一看吧。 首先需要從了解轉(zhuǎn)換器的最小及最大輸出電壓入手。這只是將所有LED正向壓降與傳感電阻器電壓相加的總數(shù)
    發(fā)表于 10-10 15:07

    介紹UPS電源的四種工作方式

    UPS電源是較為常見的應(yīng)急電源系統(tǒng),其在市電正常與市電異常的情況下,工作方式也有所不同,以下介紹UPS電源的四種工作方式:正常運(yùn)行、電池工作、旁路運(yùn)行和旁路維護(hù)。1、正常運(yùn)行方式 UPS電源系統(tǒng)
    發(fā)表于 11-16 06:19

    介紹AUTOSAR支持的四種功能安全機(jī)制

    1、AUTOSAR的四種功能安全機(jī)制雖然AUTOSAR不是一個(gè)完整的安全解決方案,但它提供了一些安全機(jī)制用于支持安全關(guān)鍵系統(tǒng)的開發(fā)。本文用于介紹AUTOSAR支持的四種功能安全機(jī)制:內(nèi)存分區(qū)
    發(fā)表于 06-10 17:33

    四種典型瞬態(tài)介紹

    ,我將介紹應(yīng)該注意的幾種典型瞬態(tài),以及TI如何幫助滿足瞬態(tài)保護(hù)需求。 瀏覽此文章,并查看參考設(shè)計(jì):《汽車瞬態(tài)和過流保護(hù)濾波器參考設(shè)計(jì)》 典型瞬態(tài)在四種常見場(chǎng)景中可能會(huì)發(fā)生瞬變。圖1所示為第一場(chǎng)景
    發(fā)表于 11-07 08:02

    無線充電技術(shù)的四種方式及其原理和應(yīng)用介紹

    本文介紹了無線充電技術(shù)的應(yīng)用范圍及其電磁感應(yīng)方式等四種充電方式的詳細(xì)介紹。
    發(fā)表于 10-12 16:16 ?27次下載
    無線充電技術(shù)的<b class='flag-5'>四種</b>方式及其原理和應(yīng)用<b class='flag-5'>介紹</b>

    jquery四種選擇器介紹

      本文給大家匯總介紹了jQuery的四種選擇器的使用方法以及示例,非常的簡(jiǎn)單實(shí)用,希望對(duì)大家學(xué)習(xí)jquery能夠有所幫助。
    發(fā)表于 12-01 16:40 ?2992次閱讀
    jquery<b class='flag-5'>四種</b>選擇器<b class='flag-5'>介紹</b>

    四種溫度傳感器的數(shù)據(jù)介紹

    本文檔的主要內(nèi)容詳細(xì)介紹的是四種溫度傳感器的數(shù)據(jù)介紹包括了:  球形傳感器,卡箍式傳感器,地面?zhèn)鞲衅?,侵入式傳感?/div>
    發(fā)表于 02-28 17:09 ?9次下載

    四種常見的圖像濾波算法介紹

    作者丨一支程序媛@知乎 來源丨h(huán)ttps://zhuanlan.zhihu.com/p/279602383 編輯丨極市平臺(tái) 導(dǎo)讀 圖像濾波是一非常重要的圖像處理技術(shù),本文詳細(xì)介紹四種常見的圖像
    的頭像 發(fā)表于 02-15 09:50 ?9780次閱讀

    四種方式實(shí)現(xiàn)led點(diǎn)亮

    四種方式實(shí)現(xiàn)led點(diǎn)亮
    發(fā)表于 01-04 14:31 ?4次下載

    NoSQL數(shù)據(jù)庫(kù)的四種類型

    在本文中,我們將簡(jiǎn)要介紹NoSQL數(shù)據(jù)庫(kù)的四種類型。
    的頭像 發(fā)表于 04-25 17:21 ?4048次閱讀

    synchronized的鎖膨脹

    初識(shí) synchronized 可以加在方法和類上面,作用于類和對(duì)象。下面代碼中列出了 synchronized用法。 public class SynchronizedTest
    的頭像 發(fā)表于 10-10 16:58 ?390次閱讀
    <b class='flag-5'>synchronized</b>的鎖膨脹

    介紹MCUboot支持的四種升級(jí)模式(2)

    介紹MCUboot支持的四種升級(jí)模式,分別是Overwrite、Swap、Direct XIP和加載到RAM中執(zhí)行。由于FSP不支持第四種——加載到RAM中執(zhí)行,因?yàn)槲覀冎攸c(diǎn)介紹前三
    的頭像 發(fā)表于 06-13 10:56 ?514次閱讀
    <b class='flag-5'>介紹</b>MCUboot支持的<b class='flag-5'>四種</b>升級(jí)模式(2)