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

Synchronized?和?ReentrantLock?的實(shí)現(xiàn)原理及區(qū)別

 更新時(shí)間:2022年09月16日 09:01:40   作者:Mr_Fire  
這篇文章主要介紹了Synchronized?和?ReentrantLock?的實(shí)現(xiàn)原理及區(qū)別,文章為榮啊主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下

前言

在 JDK 1.5 之前共享對(duì)象的協(xié)調(diào)機(jī)制只有 synchronized 和 volatile,在 JDK 1.5 中增加了新的機(jī)制 ReentrantLock,該機(jī)制的誕生并不是為了替代 synchronized,而是在 synchronized 不適用的情況下,提供一種可以選擇的高級(jí)功能。

典型回答:

synchronized 屬于獨(dú)占式悲觀鎖,是通過 JVM 隱式實(shí)現(xiàn)的,synchronized 只允許同一時(shí)刻只有一個(gè)線程操作資源。

在 Java 中每個(gè)對(duì)象都隱式包含一個(gè) monitor(監(jiān)視器)對(duì)象,加鎖的過程其實(shí)就是競爭 monitor 的過程,當(dāng)線程進(jìn)入字節(jié)碼 monitorenter 指令之后,線程將持有 monitor 對(duì)象,執(zhí)行 monitorexit 時(shí)釋放 monitor 對(duì)象,當(dāng)其他線程沒有拿到 monitor 對(duì)象時(shí),則需要阻塞等待獲取該對(duì)象。

ReentrantLock 是 Lock 的默認(rèn)實(shí)現(xiàn)方式之一,它是基于 AQS(Abstract Queued Synchronizer,隊(duì)列同步器)實(shí)現(xiàn)的,它默認(rèn)是通過非公平鎖實(shí)現(xiàn)的,在它的內(nèi)部有一個(gè) state 的狀態(tài)字段用于表示鎖是否被占用,如果是 0 則表示鎖未被占用,此時(shí)線程就可以把 state 改為 1,并成功獲得鎖,而其他未獲得鎖的線程只能去排隊(duì)等待獲取鎖資源。

synchronized 和 ReentrantLock 都提供了鎖的功能,具備互斥性和不可見性。在 JDK 1.5 中 synchronized 的性能遠(yuǎn)遠(yuǎn)低于 ReentrantLock,但在 JDK 1.6 之后 synchronized 的性能略低于 ReentrantLock,它的區(qū)別如下:

  • synchronized 是 JVM 隱式實(shí)現(xiàn)的,而 ReentrantLock 是 Java 語言提供的 API;
  • ReentrantLock 可設(shè)置為公平鎖,而 synchronized 卻不行;
  • ReentrantLock 只能修飾代碼塊,而 synchronized 可以用于修飾方法、修飾代碼塊等;
  • ReentrantLock 需要手動(dòng)加鎖和釋放鎖,如果忘記釋放鎖,則會(huì)造成資源被永久占用,而 synchronized 無需手動(dòng)釋放鎖;
  • ReentrantLock 可以知道是否成功獲得了鎖,而 synchronized 卻不行。

考點(diǎn)分析

synchronized 和 ReentrantLock 是比線程池還要高頻的面試問題,因?yàn)樗烁嗟闹R(shí)點(diǎn),且涉及到的知識(shí)點(diǎn)更加深入,對(duì)面試者的要求也更高,前面我們簡要地介紹了 synchronized 和 ReentrantLock 的概念及執(zhí)行原理,但很多大廠會(huì)更加深入的追問更多關(guān)于它們的實(shí)現(xiàn)細(xì)節(jié),比如:

  • ReentrantLock 的具體實(shí)現(xiàn)細(xì)節(jié)是什么?
  • JDK 1.6 時(shí)鎖做了哪些優(yōu)化?

知識(shí)擴(kuò)展

ReentrantLock 源碼分析

從源碼出發(fā)來解密 ReentrantLock 的具體實(shí)現(xiàn)細(xì)節(jié),首先來看 ReentrantLock 的兩個(gè)構(gòu)造函數(shù):

public ReentrantLock() {
    sync = new NonfairSync(); // 非公平鎖
}
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

無參的構(gòu)造函數(shù)創(chuàng)建了一個(gè)非公平鎖,用戶也可以根據(jù)第二個(gè)構(gòu)造函數(shù),設(shè)置一個(gè) boolean 類型的值,來決定是否使用公平鎖來實(shí)現(xiàn)線程的調(diào)度。

公平鎖 VS 非公平鎖

公平鎖的含義是線程需要按照請求的順序來獲得鎖;而非公平鎖則允許“插隊(duì)”的情況存在,所謂的“插隊(duì)”指的是,線程在發(fā)送請求的同時(shí)該鎖的狀態(tài)恰好變成了可用,那么此線程就可以跳過隊(duì)列中所有排隊(duì)的線程直接擁有該鎖。

而公平鎖由于有掛起和恢復(fù)所以存在一定的開銷,因此性能不如非公平鎖,所以 ReentrantLock 和 synchronized 默認(rèn)都是非公平鎖的實(shí)現(xiàn)方式。

ReentrantLock 是通過 lock() 來獲取鎖,并通過 unlock() 釋放鎖,使用代碼如下:

Lock lock = new ReentrantLock();
try {
    // 加鎖
    lock.lock();
    //......業(yè)務(wù)處理
} finally {
    // 釋放鎖
    lock.unlock();
}

ReentrantLock 中的 lock() 是通過 sync.lock() 實(shí)現(xiàn)的,但 Sync 類中的 lock() 是一個(gè)抽象方法,需要子類 NonfairSync 或 FairSync 去實(shí)現(xiàn),NonfairSync 中的 lock() 源碼如下:

final void lock() {
    if (compareAndSetState(0, 1))
        // 將當(dāng)前線程設(shè)置為此鎖的持有者
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}

FairSync 中的 lock() 源碼如下:

final void lock() {
    acquire(1);
}

可以看出非公平鎖比公平鎖只是多了一行 compareAndSetState 方法,該方法是嘗試將 state 值由 0 置換為 1,如果設(shè)置成功的話,則說明當(dāng)前沒有其他線程持有該鎖,不用再去排隊(duì)了,可直接占用該鎖,否則,則需要通過 acquire 方法去排隊(duì)。

acquire 源碼如下:

public final void acquire(int arg) {
  if (!tryAcquire(arg) &&
  acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
  selfInterrupt();
}

tryAcquire 方法嘗試獲取鎖,如果獲取鎖失敗,則把它加入到阻塞隊(duì)列中,來看 tryAcquire 的源碼:

protected?final?boolean?tryAcquire(int?acquires)?{
????final?Thread?current?=?Thread.currentThread();
????int?c?=?getState();
????if?(c?==?0)?{
????????//?公平鎖比非公平鎖多了一行代碼?!hasQueuedPredecessors()?
????????if?(!hasQueuedPredecessors()?&&
????????????compareAndSetState(0,?acquires))?{?//嘗試獲取鎖
????????????setExclusiveOwnerThread(current);?//?獲取成功,標(biāo)記被搶占
????????????return?true;
????????}
????}
????else?if?(current?==?getExclusiveOwnerThread())?{
????????int?nextc?=?c?+?acquires;
????????if?(nextc?<?0)
????????????throw?new?Error("Maximum?lock?count?exceeded");
????????setState(nextc);?//?set?state=state+1
????????return?true;
????}
????return?false;
}

對(duì)于此方法來說,公平鎖比非公平鎖只多一行代碼 !hasQueuedPredecessors(),它用來查看隊(duì)列中是否有比它等待時(shí)間更久的線程,如果沒有,就嘗試一下是否能獲取到鎖,如果獲取成功,則標(biāo)記為已經(jīng)被占用。

如果獲取鎖失敗,則調(diào)用 addWaiter 方法把線程包裝成 Node 對(duì)象,同時(shí)放入到隊(duì)列中,但 addWaiter 方法并不會(huì)嘗試獲取鎖,acquireQueued 方法才會(huì)嘗試獲取鎖,如果獲取失敗,則此節(jié)點(diǎn)會(huì)被掛起,源碼如下:

/**
?*?隊(duì)列中的線程嘗試獲取鎖,失敗則會(huì)被掛起
?*/
final?boolean?acquireQueued(final?Node?node,?int?arg)?{
????boolean?failed?=?true;?//?獲取鎖是否成功的狀態(tài)標(biāo)識(shí)
????try?{
????????boolean?interrupted?=?false;?//?線程是否被中斷
????????for?(;;)?{
????????????//?獲取前一個(gè)節(jié)點(diǎn)(前驅(qū)節(jié)點(diǎn))
????????????final?Node?p?=?node.predecessor();
????????????//?當(dāng)前節(jié)點(diǎn)為頭節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)時(shí),有權(quán)嘗試獲取鎖
????????????if?(p?==?head?&&?tryAcquire(arg))?{
????????????????setHead(node);?//?獲取成功,將當(dāng)前節(jié)點(diǎn)設(shè)置為?head?節(jié)點(diǎn)
????????????????p.next?=?null;?//?原?head?節(jié)點(diǎn)出隊(duì),等待被?GC
????????????????failed?=?false;?//?獲取成功
????????????????return?interrupted;
????????????}
????????????//?判斷獲取鎖失敗后是否可以掛起
????????????if?(shouldParkAfterFailedAcquire(p,?node)?&&
????????????????parkAndCheckInterrupt())
????????????????//?線程若被中斷,返回?true
????????????????interrupted?=?true;
????????}
????}?finally?{
????????if?(failed)
????????????cancelAcquire(node);
????}
}

該方法會(huì)使用 for(;;) 無限循環(huán)的方式來嘗試獲取鎖,若獲取失敗,則調(diào)用 shouldParkAfterFailedAcquire 方法,嘗試掛起當(dāng)前線程,源碼如下:

/**
?*?判斷線程是否可以被掛起
?*/
private?static?boolean?shouldParkAfterFailedAcquire(Node?pred,?Node?node)?{
????//?獲得前驅(qū)節(jié)點(diǎn)的狀態(tài)
????int?ws?=?pred.waitStatus;
????//?前驅(qū)節(jié)點(diǎn)的狀態(tài)為?SIGNAL,當(dāng)前線程可以被掛起(阻塞)
????if?(ws?==?Node.SIGNAL)
????????return?true;
????if?(ws?>?0)?{?
????????do?{
????????//?若前驅(qū)節(jié)點(diǎn)狀態(tài)為?CANCELLED,那就一直往前找,直到找到一個(gè)正常等待的狀態(tài)為止
????????????node.prev?=?pred?=?pred.prev;
????????}?while?(pred.waitStatus?>?0);
????????//?并將當(dāng)前節(jié)點(diǎn)排在它后邊
????????pred.next?=?node;
????}?else?{
????????//?把前驅(qū)節(jié)點(diǎn)的狀態(tài)修改為?SIGNAL
????????compareAndSetWaitStatus(pred,?ws,?Node.SIGNAL);
????}
????return?false;
}

線程入列被掛起的前提條件是,前驅(qū)節(jié)點(diǎn)的狀態(tài)為 SIGNAL,SIGNAL 狀態(tài)的含義是后繼節(jié)點(diǎn)處于等待狀態(tài),當(dāng)前節(jié)點(diǎn)釋放鎖后將會(huì)喚醒后繼節(jié)點(diǎn)。所以在上面這段代碼中,會(huì)先判斷前驅(qū)節(jié)點(diǎn)的狀態(tài),如果為 SIGNAL,則當(dāng)前線程可以被掛起并返回 true;如果前驅(qū)節(jié)點(diǎn)的狀態(tài) >0,則表示前驅(qū)節(jié)點(diǎn)取消了,這時(shí)候需要一直往前找,直到找到最近一個(gè)正常等待的前驅(qū)節(jié)點(diǎn),然后把它作為自己的前驅(qū)節(jié)點(diǎn);如果前驅(qū)節(jié)點(diǎn)正常(未取消),則修改前驅(qū)節(jié)點(diǎn)狀態(tài)為 SIGNAL。

到這里整個(gè)加鎖的流程就已經(jīng)走完了,最后的情況是,沒有拿到鎖的線程會(huì)在隊(duì)列中被掛起,直到擁有鎖的線程釋放鎖之后,才會(huì)去喚醒其他的線程去獲取鎖資源,整個(gè)運(yùn)行流程如下圖所示:

unlock 相比于 lock 來說就簡單很多了,源碼如下:

public?void?unlock()?{
????sync.release(1);
}
public?final?boolean?release(int?arg)?{
????//?嘗試釋放鎖
????if?(tryRelease(arg))?{
????????//?釋放成功
????????Node?h?=?head;
????????if?(h?!=?null?&&?h.waitStatus?!=?0)
????????????unparkSuccessor(h);
????????return?true;
????}
????return?false;
}

鎖的釋放流程為,先調(diào)用 tryRelease 方法嘗試釋放鎖,如果釋放成功,則查看頭結(jié)點(diǎn)的狀態(tài)是否為 SIGNAL,如果是,則喚醒頭結(jié)點(diǎn)的下個(gè)節(jié)點(diǎn)關(guān)聯(lián)的線程;如果釋放鎖失敗,則返回 false。

tryRelease 源碼如下:

/**
?*?嘗試釋放當(dāng)前線程占有的鎖
?*/
protected?final?boolean?tryRelease(int?releases)?{
????int?c?=?getState()?-?releases;?//?釋放鎖后的狀態(tài),0?表示釋放鎖成功
????//?如果擁有鎖的線程不是當(dāng)前線程的話拋出異常
????if?(Thread.currentThread()?!=?getExclusiveOwnerThread())
????????throw?new?IllegalMonitorStateException();
????boolean?free?=?false;
????if?(c?==?0)?{?//?鎖被成功釋放
????????free?=?true;
????????setExclusiveOwnerThread(null);?//?清空獨(dú)占線程
????}
????setState(c);?//?更新?state?值,0?表示為釋放鎖成功
????return?free;
}

在 tryRelease 方法中,會(huì)先判斷當(dāng)前的線程是不是占用鎖的線程,如果不是的話,則會(huì)拋出異常;如果是的話,則先計(jì)算鎖的狀態(tài)值 getState() - releases 是否為 0,如果為 0,則表示可以正常的釋放鎖,然后清空獨(dú)占的線程,最后會(huì)更新鎖的狀態(tài)并返回執(zhí)行結(jié)果。

JDK 1.6 鎖優(yōu)化

自適應(yīng)自旋鎖

JDK 1.5 在升級(jí)為 JDK 1.6 時(shí),HotSpot 虛擬機(jī)團(tuán)隊(duì)在鎖的優(yōu)化上下了很大功夫,比如實(shí)現(xiàn)了自適應(yīng)式自旋鎖、鎖升級(jí)等。

JDK 1.6 引入了自適應(yīng)式自旋鎖意味著自旋的時(shí)間不再是固定的時(shí)間了,比如在同一個(gè)鎖對(duì)象上,如果通過自旋等待成功獲取了鎖,那么虛擬機(jī)就會(huì)認(rèn)為,它下一次很有可能也會(huì)成功 (通過自旋獲取到鎖),因此允許自旋等待的時(shí)間會(huì)相對(duì)的比較長,而當(dāng)某個(gè)鎖通過自旋很少成功獲得過鎖,那么以后在獲取該鎖時(shí),可能會(huì)直接忽略掉自旋的過程,以避免浪費(fèi) CPU 的資源,這就是自適應(yīng)自旋鎖的功能。

鎖升級(jí)

鎖升級(jí)其實(shí)就是從偏向鎖到輕量級(jí)鎖再到重量級(jí)鎖升級(jí)的過程,這是 JDK 1.6 提供的優(yōu)化功能,也稱之為鎖膨脹。

偏向鎖是指在無競爭的情況下設(shè)置的一種鎖狀態(tài)。偏向鎖的意思是它會(huì)偏向于第一個(gè)獲取它的線程,當(dāng)鎖對(duì)象第一次被獲取到之后,會(huì)在此對(duì)象頭中設(shè)置標(biāo)示為“01”,表示偏向鎖的模式,并且在對(duì)象頭中記錄此線程的 ID,這種情況下,如果是持有偏向鎖的線程每次在進(jìn)入的話,不再進(jìn)行任何同步操作,如 Locking、Unlocking 等,直到另一個(gè)線程嘗試獲取此鎖的時(shí)候,偏向鎖模式才會(huì)結(jié)束,偏向鎖可以提高帶有同步但無競爭的程序性能。但如果在多數(shù)鎖總會(huì)被不同的線程訪問時(shí),偏向鎖模式就比較多余了,此時(shí)可以通過 -XX:-UseBiasedLocking 來禁用偏向鎖以提高性能。

輕量鎖是相對(duì)于重量鎖而言的,在 JDK 1.6 之前,synchronized 是通過操作系統(tǒng)的互斥量(mutex lock)來實(shí)現(xiàn)的,這種實(shí)現(xiàn)方式需要在用戶態(tài)和核心態(tài)之間做轉(zhuǎn)換,有很大的性能消耗,這種傳統(tǒng)實(shí)現(xiàn)鎖的方式被稱之為重量鎖。

而輕量鎖是通過比較并交換(CAS,Compare and Swap)來實(shí)現(xiàn)的,它對(duì)比的是線程和對(duì)象的 Mark Word(對(duì)象頭中的一個(gè)區(qū)域),如果更新成功則表示當(dāng)前線程成功擁有此鎖;如果失敗,虛擬機(jī)會(huì)先檢查對(duì)象的 Mark Word 是否指向當(dāng)前線程的棧幀,如果是,則說明當(dāng)前線程已經(jīng)擁有此鎖,否則,則說明此鎖已經(jīng)被其他線程占用了。當(dāng)兩個(gè)以上的線程爭搶此鎖時(shí),輕量級(jí)鎖就膨脹為重量級(jí)鎖,這就是鎖升級(jí)的過程,也是 JDK 1.6 鎖優(yōu)化的內(nèi)容。

總結(jié)

本文首先講了 synchronized 和 ReentrantLock 的實(shí)現(xiàn)過程,然后講了 synchronized 和 ReentrantLock 的區(qū)別,最后通過源碼的方式講了 ReentrantLock 加鎖和解鎖的執(zhí)行流程。接著又講了 JDK 1.6 中的鎖優(yōu)化,包括自適應(yīng)式自旋鎖的實(shí)現(xiàn)過程,以及 synchronized 的三種鎖狀態(tài)和鎖升級(jí)的執(zhí)行流程。

synchronized 剛開始為偏向鎖,隨著鎖競爭越來越激烈,會(huì)升級(jí)為輕量級(jí)鎖和重量級(jí)鎖。如果大多數(shù)鎖被不同的線程所爭搶就不建議使用偏向鎖了。

到此這篇關(guān)于Synchronized 和 ReentrantLock 的實(shí)現(xiàn)原理及區(qū)別的文章就介紹到這了,更多相關(guān)Synchronized 與 ReentrantLock 內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Springboot如何實(shí)現(xiàn)Web系統(tǒng)License授權(quán)認(rèn)證

    Springboot如何實(shí)現(xiàn)Web系統(tǒng)License授權(quán)認(rèn)證

    這篇文章主要介紹了Springboot如何實(shí)現(xiàn)Web系統(tǒng)License授權(quán)認(rèn)證,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-05-05
  • java中樂觀鎖與悲觀鎖區(qū)別及使用場景分析

    java中樂觀鎖與悲觀鎖區(qū)別及使用場景分析

    本文主要介紹了java中樂觀鎖與悲觀鎖區(qū)別及使用場景分析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2024-08-08
  • Spring實(shí)戰(zhàn)之XML與JavaConfig的混合配置詳解

    Spring實(shí)戰(zhàn)之XML與JavaConfig的混合配置詳解

    大家都知道Spring的顯示配置方式有兩種,一種是基于XML配置,一種是基于JavaConfig的方式配置。那么下這篇文章主要給大家分別介紹如何在JavaConfig中引用XML配置的bean以及如何在XML配置中引用JavaConfig,需要的朋友可以參考下。
    2017-07-07
  • java常用工具類 XML工具類、數(shù)據(jù)驗(yàn)證工具類

    java常用工具類 XML工具類、數(shù)據(jù)驗(yàn)證工具類

    這篇文章主要為大家詳細(xì)介紹了java常用工具類,包括XML工具類、數(shù)據(jù)驗(yàn)證工具類,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-05-05
  • kafka運(yùn)維consumer-groups.sh消費(fèi)者組管理

    kafka運(yùn)維consumer-groups.sh消費(fèi)者組管理

    這篇文章主要為大家介紹了kafka運(yùn)維consumer-groups.sh消費(fèi)者組管理,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-11-11
  • Java實(shí)現(xiàn)銀行存取款

    Java實(shí)現(xiàn)銀行存取款

    這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)銀行存取款,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-12-12
  • springbean的八種加載方式匯總

    springbean的八種加載方式匯總

    這篇文章主要介紹了springbean的八種加載方式,一種是XML方式聲明bean,使用@Component及其衍生注解@Controller?、@Service、@Repository定義bean,還有其他方法,本文給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2022-10-10
  • Oracle + Mybatis實(shí)現(xiàn)批量插入、更新和刪除示例代碼

    Oracle + Mybatis實(shí)現(xiàn)批量插入、更新和刪除示例代碼

    利用MyBatis動(dòng)態(tài)SQL的特性,我們可以做一些批量的操作,下面這篇文章主要給大家介紹了關(guān)于Oracle + Mybatis實(shí)現(xiàn)批量插入、更新和刪除的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面來一起看看吧。
    2018-01-01
  • MyBatis批量添加數(shù)據(jù)2種實(shí)現(xiàn)方法

    MyBatis批量添加數(shù)據(jù)2種實(shí)現(xiàn)方法

    這篇文章主要介紹了MyBatis批量添加數(shù)據(jù)2種實(shí)現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-06-06
  • SpringBoot 多任務(wù)并行+線程池處理的實(shí)現(xiàn)

    SpringBoot 多任務(wù)并行+線程池處理的實(shí)現(xiàn)

    這篇文章主要介紹了SpringBoot 多任務(wù)并行+線程池處理的實(shí)現(xiàn),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-04-04

最新評(píng)論