欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java并發(fā)編程中的synchronized關(guān)鍵字詳細(xì)解讀

 更新時(shí)間:2023年12月12日 10:36:01   作者:程光CS  
這篇文章主要介紹了Java并發(fā)編程中的synchronized關(guān)鍵字詳細(xì)解讀,在Java早期版本中,synchronized 屬于 重量級(jí)鎖,效率低下,這是因?yàn)楸O(jiān)視器鎖(monitor)是依賴(lài)于底層的操作系統(tǒng)的Mutex Lock來(lái)實(shí)現(xiàn)的,Java 的線(xiàn)程是映射到操作系統(tǒng)的原生線(xiàn)程之上的,需要的朋友可以參考下

前言

在 Java 早期版本中,synchronized 屬于 重量級(jí)鎖,效率低下。這是因?yàn)楸O(jiān)視器鎖(monitor)是依賴(lài)于底層的操作系統(tǒng)的 Mutex Lock 來(lái)實(shí)現(xiàn)的,Java 的線(xiàn)程是映射到操作系統(tǒng)的原生線(xiàn)程之上的。如果要掛起或者喚醒一個(gè)線(xiàn)程,都需要操作系統(tǒng)幫忙完成,而操作系統(tǒng)實(shí)現(xiàn)線(xiàn)程之間的切換時(shí)需要從用戶(hù)態(tài)轉(zhuǎn)換到內(nèi)核態(tài),這個(gè)狀態(tài)之間的轉(zhuǎn)換需要相對(duì)比較長(zhǎng)的時(shí)間,時(shí)間成本相對(duì)較高。在 Java 6 之后, synchronized 引入了大量的優(yōu)化如自旋鎖、適應(yīng)性自旋鎖、鎖消除、鎖粗化、偏向鎖、輕量級(jí)鎖等技術(shù)來(lái)減少鎖操作的開(kāi)銷(xiāo),這些優(yōu)化讓 synchronized 鎖的效率提升了很多。

一、synchronized的使用方法

synchronized 塊是 Java 提供的一種原子性?xún)?nèi)置鎖,Java 中的每個(gè)對(duì)象都可以把它當(dāng)作一個(gè)同步鎖來(lái)使用。

synchronized關(guān)鍵字可以用來(lái)修飾實(shí)例方法、靜態(tài)方法、代碼塊,表示對(duì)其進(jìn)行加鎖,當(dāng)線(xiàn)程進(jìn)入 synchronized 代碼塊前只有獲取到相應(yīng)的鎖才能訪(fǎng)問(wèn),否則自動(dòng)進(jìn)入自旋或阻塞狀態(tài)(BLOCKED)等待鎖被其他線(xiàn)程釋放后競(jìng)爭(zhēng)鎖。

1、修飾實(shí)例方法 (鎖當(dāng)前對(duì)象實(shí)例)

給當(dāng)前對(duì)象實(shí)例加鎖,進(jìn)入同步代碼前要獲得 當(dāng)前對(duì)象實(shí)例的鎖 。

synchronized void method() {
    //業(yè)務(wù)代碼
}

2、修飾靜態(tài)方法 (鎖類(lèi)對(duì)象)

給當(dāng)前類(lèi)加鎖,進(jìn)入同步代碼前要獲得 當(dāng)前類(lèi)class對(duì)象的鎖。這是因?yàn)殪o態(tài)成員不屬于任何一個(gè)實(shí)例對(duì)象,歸整個(gè)類(lèi)所有,不依賴(lài)于類(lèi)的特定實(shí)例。

synchronized static void method() {
    //業(yè)務(wù)代碼
}

3、修飾代碼塊 (鎖指定對(duì)象/類(lèi)對(duì)象)

  • synchronized(object) 表示進(jìn)入同步代碼前要獲得 給定對(duì)象的鎖。
  • synchronized(類(lèi).class) 表示進(jìn)入同步代碼前要獲得 給定 Class對(duì)象 的鎖。
synchronized(this) {
    //業(yè)務(wù)代碼
}

二、synchronized的特性

1. 可重入鎖

持有鎖的線(xiàn)程可直接進(jìn)入此鎖關(guān)聯(lián)的任意其他代碼。

2. 非公平鎖

不是按照先來(lái)后到的原則來(lái)分配鎖。

3. 不可中斷鎖

synchronized在鎖競(jìng)爭(zhēng)時(shí)是不可中斷的,獲取不到鎖的線(xiàn)程會(huì)一直處于阻塞狀態(tài)。而ReentrantLock獲取鎖失敗可以被interrupt()進(jìn)行中斷操作。

三、synchronized相關(guān)問(wèn)題

1. volatile和synchronized的區(qū)別是什么?

  • volatile 關(guān)鍵字用于修飾變量,可保證變量的可見(jiàn)性和有序性。
  • synchronized關(guān)鍵字用于修飾方法或代碼塊,可保證代碼塊的原子性以及代碼塊內(nèi)變量的可見(jiàn)性,以及代碼塊外部和內(nèi)部之間的有序性(代碼塊內(nèi)部的有序性不保證,例如DCL單例指令重排問(wèn)題)。

2. 占有鎖的線(xiàn)程在什么情況下會(huì)釋放鎖?

  • 占有鎖的線(xiàn)程執(zhí)行完了該代碼塊,然后釋放對(duì)鎖的占有;
  • 占有鎖線(xiàn)程執(zhí)行發(fā)生異常,此時(shí)JVM會(huì)讓線(xiàn)程自動(dòng)釋放鎖;
  • 占有鎖線(xiàn)程進(jìn)入 WAITING 狀態(tài)從而釋放鎖,例如在該線(xiàn)程中調(diào)用wait()方法等

四、synchronized的底層原理

對(duì)于synchronized同步代碼塊,編譯后在代碼塊前后分別有一個(gè)monitorenter 和 monitorexit 指令,在JVM中當(dāng)線(xiàn)程執(zhí)行到monitorenter指令時(shí)嘗試獲取指定對(duì)象的鎖,執(zhí)行到monitorexit 指令則釋放鎖。

在這里插入圖片描述

對(duì)于synchronized同步方法,編譯后方法中有一個(gè)ACC_SYNCHRONIZED標(biāo)識(shí),在JVM中當(dāng)線(xiàn)程執(zhí)行到有此標(biāo)識(shí)的方法時(shí)會(huì)隱式調(diào)用monitorenter和monitorexit。在執(zhí)行同步方法前會(huì)調(diào)用monitorenter,在執(zhí)行完同步方法后會(huì)調(diào)用monitorexit。最后都能達(dá)到加鎖的效果。

在這里插入圖片描述

五、synchronized的鎖升級(jí)過(guò)程

早期synchronized實(shí)現(xiàn)的同步鎖為重量級(jí)鎖。但是重量級(jí)鎖會(huì)造成線(xiàn)程阻塞排隊(duì),阻塞和喚醒線(xiàn)程會(huì)使CPU在用戶(hù)態(tài)和核心態(tài)之間頻繁切換,所以代價(jià)高、效率低。因此 Java6 對(duì) synchronized 鎖進(jìn)行了優(yōu)化,增加了輕量級(jí)鎖和偏向鎖。為了提高效率,不會(huì)一開(kāi)始就使用重量級(jí)鎖,JVM在內(nèi)部會(huì)根據(jù)需要,按如下步驟進(jìn)行鎖的升級(jí):

在這里插入圖片描述

1. 無(wú)鎖狀態(tài)

初期鎖對(duì)象剛創(chuàng)建時(shí),還沒(méi)有任何線(xiàn)程來(lái)競(jìng)爭(zhēng),對(duì)象的markword是上圖的第一種情形,這偏向鎖標(biāo)識(shí)位是0,鎖狀態(tài)01,說(shuō)明該對(duì)象處于無(wú)鎖狀態(tài)(無(wú)線(xiàn)程競(jìng)爭(zhēng)它)。

2. 偏向鎖

當(dāng)有一個(gè)線(xiàn)程來(lái)競(jìng)爭(zhēng)鎖時(shí),先用偏向鎖,會(huì)在對(duì)象頭的markword中記錄線(xiàn)程threadID,并且線(xiàn)程退出synchronized塊后偏向鎖不會(huì)主動(dòng)釋放,因此之后此線(xiàn)程需要再次獲取鎖的時(shí)候,通過(guò)比較當(dāng)前線(xiàn)程的 threadID 和 對(duì)象頭中的threadID 發(fā)現(xiàn)一致,就不需要再做任何檢查和切換直接進(jìn)入,這種競(jìng)爭(zhēng)不激烈的情況下,效率非常高。如上圖第二種情形。

JDK 15中的偏向鎖 偏向鎖在單線(xiàn)程反復(fù)獲取鎖的場(chǎng)景下性能很高,但細(xì)想便知生產(chǎn)環(huán)境中高并發(fā)的場(chǎng)景下很難有這種場(chǎng)景。 而且對(duì)于偏向鎖來(lái)說(shuō),在多線(xiàn)程競(jìng)爭(zhēng)時(shí)的撤銷(xiāo)操作十分復(fù)雜且?guī)?lái)了額外的性能消耗(需要等到safe point,并STW)。 JDK 15 之前,偏向鎖默認(rèn)是 開(kāi)啟的,從 JDK 15 開(kāi)始,默認(rèn)就是關(guān)閉的了,需要顯式打開(kāi)(-XX:+UseBiasedLocking)。

3. 輕量級(jí)鎖

當(dāng)需要獲取對(duì)象的hashcode值時(shí)就會(huì)禁用偏向鎖升級(jí)為輕量級(jí)鎖,將hashcode值寫(xiě)入markword;或者當(dāng)有第二個(gè)線(xiàn)程開(kāi)始競(jìng)爭(zhēng)這個(gè)鎖對(duì)象,通過(guò)對(duì)比markword中記錄線(xiàn)程threadID發(fā)現(xiàn)不一致,那么首先需要查看Java 對(duì)象頭中記錄的線(xiàn)程 1 是否存活(偏向鎖不會(huì)主動(dòng)釋放鎖),如果沒(méi)有存活,那么鎖對(duì)象被重置為無(wú)鎖狀態(tài),其它線(xiàn)程(線(xiàn)程 2)可以競(jìng)爭(zhēng)將其設(shè)置為偏向鎖;如果存活,那么立刻查找該線(xiàn)程(線(xiàn)程 1)的棧幀信息,如果還是需要繼續(xù)持有這個(gè)鎖對(duì)象,那么暫停當(dāng)前線(xiàn)程 1,撤銷(xiāo)偏向鎖,升級(jí)為 輕量級(jí)鎖,如果線(xiàn)程 1 不再使用該鎖對(duì)象,那么將鎖對(duì)象狀態(tài)設(shè)為無(wú)鎖狀態(tài),重新偏向新的線(xiàn)程。如上圖第三種情形。

在這里插入圖片描述

4. 重量級(jí)鎖(monitor)

當(dāng)輕量級(jí)鎖等待獲取鎖的線(xiàn)程自旋到達(dá)一定次數(shù),或者競(jìng)爭(zhēng)的這個(gè)鎖對(duì)象的線(xiàn)程更多,導(dǎo)致了更多的切換和等待,JVM會(huì)把該鎖對(duì)象的鎖升級(jí)為重量級(jí)鎖。synchronized的重量級(jí)鎖是基于在監(jiān)視器(monitor)實(shí)現(xiàn)的,JVM中每個(gè)對(duì)象都可以關(guān)聯(lián)一個(gè)ObjectMonitor監(jiān)視器對(duì)象(C++實(shí)現(xiàn)),升級(jí)為重量級(jí)鎖后對(duì)象的Mark Word再次發(fā)生變化,會(huì)指向?qū)ο箨P(guān)聯(lián)的監(jiān)視器對(duì)象(如上圖第四種情形)。

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

 ObjectMonitor() {
    _header       = NULL;
    _count        = 0;
    _waiters      = 0,
    _recursions   = 0;      //線(xiàn)程重入次數(shù)
    _object       = NULL;   //指向?qū)?yīng)的java對(duì)象
    _owner        = NULL;   //持有鎖的線(xiàn)程
    _WaitSet      = NULL;   //等待隊(duì)列:處于wait狀態(tài)的線(xiàn)程會(huì)被加入到這個(gè)隊(duì)列中
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ;  //要競(jìng)爭(zhēng)鎖的線(xiàn)程會(huì)先被加入到這個(gè)隊(duì)列中
    FreeNext      = NULL ;
    _EntryList    = NULL ;  //處于blocked阻塞狀態(tài)的線(xiàn)程,會(huì)被加入到這個(gè)隊(duì)列中
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
    _previous_owner_tid = 0;
  }

鎖競(jìng)爭(zhēng)機(jī)制:

  • 當(dāng)線(xiàn)程要獲取鎖時(shí),首先將其加入cxq隊(duì)列的頭部,獲取失敗被阻塞后則加入EntryList隊(duì)列。
  • 當(dāng)持有鎖的線(xiàn)程釋放鎖時(shí),會(huì)根據(jù)策略喚醒cxq或者EntryList隊(duì)列中的線(xiàn)程來(lái)競(jìng)爭(zhēng)鎖。
  • 當(dāng)線(xiàn)程調(diào)用wait()方法后會(huì)釋放鎖進(jìn)入阻塞狀態(tài)并加入waitSet等待隊(duì)列。當(dāng)調(diào)用notify方法后,會(huì)從waitSet中喚醒線(xiàn)程加入到cxq或者EntryList隊(duì)列。

在這里插入圖片描述

六、synchronized的其它優(yōu)化

1. 鎖粗化

原則上,我們?cè)诰帉?xiě)代碼的時(shí)候,總是推薦將同步塊的作用范圍限制得盡量小,只在共享數(shù)據(jù)的實(shí)際作用域中才進(jìn)行同步,這樣是為了使得需要同步的操作數(shù)量盡可能變小,如果存在鎖競(jìng)爭(zhēng),那等待鎖的線(xiàn)程也能盡快拿到鎖。大部分情況下,上面的原則都是正確的,但是如果一系列的連續(xù)操作都對(duì)同一個(gè)對(duì)象反復(fù)加鎖和解鎖,甚至加鎖操作是出現(xiàn)在循環(huán)體中的,那即使沒(méi)有線(xiàn)程競(jìng)爭(zhēng),頻繁地進(jìn)行互斥同步操作也會(huì)導(dǎo)致不必要的性能損耗。

public void test() {
    for (int i = 0; i < 100; i++) {
        synchronized (object) {
            i++;
        }
    }
}

我們看以上方法,在for循環(huán)中,synchronized保證每個(gè)i++操作都是原子性的。但是以上的方法有個(gè)問(wèn)題,就是在每次循環(huán)都會(huì)加鎖,開(kāi)銷(xiāo)大,效率低。

虛擬機(jī)即時(shí)編譯器(JIT)在運(yùn)行時(shí),會(huì)自動(dòng)根據(jù)synchronized的影響范圍進(jìn)行鎖粗化優(yōu)化。

優(yōu)化后代碼:

public void test() {
    synchronized (object) {
        for (int i = 0; i < 100; i++) {
            i++;
        }
    }
}

2. 鎖消除

消除鎖是虛擬機(jī)另外一種鎖的優(yōu)化,這種優(yōu)化更徹底,Java 虛擬機(jī)在 JIT 編譯時(shí)通過(guò)對(duì)運(yùn)行上下文的掃描,去除不可能存在共享資源競(jìng)爭(zhēng)的鎖,通過(guò)這種方式消除沒(méi)有必要的鎖,可以節(jié)省毫無(wú)意義的請(qǐng)求鎖時(shí)間,我們知道StringBuffer 是線(xiàn)程安全的,里面包含鎖的存在,但是如果我們?cè)诤瘮?shù)內(nèi)部使用 StringBuffer局部變量,那么代碼會(huì)在 JIT 后會(huì)自動(dòng)將鎖消除。

到此這篇關(guān)于Java并發(fā)編程中的synchronized關(guān)鍵字詳細(xì)解讀的文章就介紹到這了,更多相關(guān)Java的synchronized關(guān)鍵字內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • java 格式化時(shí)間的示例代碼

    java 格式化時(shí)間的示例代碼

    這篇文章主要介紹了java 格式化時(shí)間的示例代碼,幫助大家更好的利用Java處理時(shí)間,感興趣的朋友可以了解下
    2020-12-12
  • Java中使用Jedis操作Redis的實(shí)現(xiàn)代碼

    Java中使用Jedis操作Redis的實(shí)現(xiàn)代碼

    本篇文章主要介紹了Java中使用Jedis操作Redis的實(shí)現(xiàn)代碼。詳細(xì)的介紹了Redis的安裝和在java中的操作,具有一定的參考價(jià)值,有興趣的可以了解一下
    2017-05-05
  • Spring發(fā)送郵件如何內(nèi)嵌圖片增加附件

    Spring發(fā)送郵件如何內(nèi)嵌圖片增加附件

    這篇文章主要介紹了Spring發(fā)送郵件如何內(nèi)嵌圖片增加附件,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-10-10
  • 詳解springboot測(cè)試類(lèi)注解

    詳解springboot測(cè)試類(lèi)注解

    這篇文章主要介紹了springboot測(cè)試類(lèi)注解,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-07-07
  • Java連接MYSQL數(shù)據(jù)庫(kù)的詳細(xì)步驟

    Java連接MYSQL數(shù)據(jù)庫(kù)的詳細(xì)步驟

    這篇文章主要為大家介紹了Java連接MYSQL數(shù)據(jù)庫(kù)的詳細(xì)步驟,感興趣的小伙伴們可以參考一下
    2016-05-05
  • Java多線(xiàn)程實(shí)現(xiàn)多人聊天室功能

    Java多線(xiàn)程實(shí)現(xiàn)多人聊天室功能

    這篇文章主要為大家詳細(xì)介紹了Java多線(xiàn)程實(shí)現(xiàn)多人聊天室功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-06-06
  • 使用Jitpack發(fā)布開(kāi)源Java庫(kù)的詳細(xì)流程

    使用Jitpack發(fā)布開(kāi)源Java庫(kù)的詳細(xì)流程

    這篇文章主要介紹了使用Jitpack發(fā)布開(kāi)源Java庫(kù)的詳細(xì)流程,本文通過(guò)圖文實(shí)例代碼相結(jié)合給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-02-02
  • JAVA異常處理機(jī)制之throws/throw使用情況

    JAVA異常處理機(jī)制之throws/throw使用情況

    這篇文章主要介紹了JAVA異常處理機(jī)制之throws/throw使用情況的區(qū)別,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-07-07
  • Maven項(xiàng)目打Jar包并添加依賴(lài)步驟詳解

    Maven項(xiàng)目打Jar包并添加依賴(lài)步驟詳解

    這篇文章主要介紹了Maven項(xiàng)目打Jar包并添加依賴(lài)步驟詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-10-10
  • Spring事務(wù)失效的各種場(chǎng)景(13種)

    Spring事務(wù)失效的各種場(chǎng)景(13種)

    本文主要介紹了Spring事務(wù)失效的各種場(chǎng)景,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2022-07-07

最新評(píng)論