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

Synchronized?和?ReentrantLock?的實現原理及區(qū)別

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

前言

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

典型回答:

synchronized 屬于獨占式悲觀鎖,是通過 JVM 隱式實現的,synchronized 只允許同一時刻只有一個線程操作資源。

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

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

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

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

考點分析

synchronized 和 ReentrantLock 是比線程池還要高頻的面試問題,因為它包含了更多的知識點,且涉及到的知識點更加深入,對面試者的要求也更高,前面我們簡要地介紹了 synchronized 和 ReentrantLock 的概念及執(zhí)行原理,但很多大廠會更加深入的追問更多關于它們的實現細節(jié),比如:

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

知識擴展

ReentrantLock 源碼分析

從源碼出發(fā)來解密 ReentrantLock 的具體實現細節(jié),首先來看 ReentrantLock 的兩個構造函數:

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

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

公平鎖 VS 非公平鎖

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

而公平鎖由于有掛起和恢復所以存在一定的開銷,因此性能不如非公平鎖,所以 ReentrantLock 和 synchronized 默認都是非公平鎖的實現方式。

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

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

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

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

FairSync 中的 lock() 源碼如下:

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

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

acquire 源碼如下:

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

tryAcquire 方法嘗試獲取鎖,如果獲取鎖失敗,則把它加入到阻塞隊列中,來看 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);?//?獲取成功,標記被搶占
????????????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;
}

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

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

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

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

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

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

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

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;
}

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

tryRelease 源碼如下:

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

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

JDK 1.6 鎖優(yōu)化

自適應自旋鎖

JDK 1.5 在升級為 JDK 1.6 時,HotSpot 虛擬機團隊在鎖的優(yōu)化上下了很大功夫,比如實現了自適應式自旋鎖、鎖升級等。

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

鎖升級

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

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

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

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

總結

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

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

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

相關文章

  • Springboot如何實現Web系統(tǒng)License授權認證

    Springboot如何實現Web系統(tǒng)License授權認證

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

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

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

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

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

    java常用工具類 XML工具類、數據驗證工具類

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

    kafka運維consumer-groups.sh消費者組管理

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

    Java實現銀行存取款

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

    springbean的八種加載方式匯總

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

    Oracle + Mybatis實現批量插入、更新和刪除示例代碼

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

    MyBatis批量添加數據2種實現方法

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

    SpringBoot 多任務并行+線程池處理的實現

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

最新評論