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

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

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

前言

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

一、synchronized的使用方法

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

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

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

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

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

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

給當(dāng)前類加鎖,進入同步代碼前要獲得 當(dāng)前類class對象的鎖。這是因為靜態(tài)成員不屬于任何一個實例對象,歸整個類所有,不依賴于類的特定實例。

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

3、修飾代碼塊 (鎖指定對象/類對象)

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

二、synchronized的特性

1. 可重入鎖

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

2. 非公平鎖

不是按照先來后到的原則來分配鎖。

3. 不可中斷鎖

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

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

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

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

2. 占有鎖的線程在什么情況下會釋放鎖?

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

四、synchronized的底層原理

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

在這里插入圖片描述

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

在這里插入圖片描述

五、synchronized的鎖升級過程

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

在這里插入圖片描述

1. 無鎖狀態(tài)

初期鎖對象剛創(chuàng)建時,還沒有任何線程來競爭,對象的markword是上圖的第一種情形,這偏向鎖標識位是0,鎖狀態(tài)01,說明該對象處于無鎖狀態(tài)(無線程競爭它)。

2. 偏向鎖

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

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

3. 輕量級鎖

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

在這里插入圖片描述

4. 重量級鎖(monitor)

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

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

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

鎖競爭機制:

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

在這里插入圖片描述

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

1. 鎖粗化

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

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

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

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

優(yōu)化后代碼:

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

2. 鎖消除

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

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

相關(guān)文章

  • java 格式化時間的示例代碼

    java 格式化時間的示例代碼

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

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

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

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

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

    詳解springboot測試類注解

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

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

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

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

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

    使用Jitpack發(fā)布開源Java庫的詳細流程

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

    JAVA異常處理機制之throws/throw使用情況

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

    Maven項目打Jar包并添加依賴步驟詳解

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

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

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

最新評論