自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

ReentrantLock核心原理,絕對(duì)干貨

開發(fā) 前端
面試官:我看你熟悉ReentrantLock源碼,能講講他的中斷鎖是怎么實(shí)現(xiàn)的么?不知道也沒關(guān)系,看完這篇文章通過你的思考,能找到答案哦!

 [[350997]]

前言

先來一個(gè)面試題:

面試官:我看你熟悉ReentrantLock源碼,能講講他的中斷鎖是怎么實(shí)現(xiàn)的么?

不知道也沒關(guān)系,看完這篇文章通過你的思考,能找到答案哦

那我們開始吧

ReentrantLock 中文我們叫做可重入互斥鎖,可重入的意思是同一個(gè)線程可以對(duì)同一個(gè)共享資源重復(fù)的加鎖或釋放鎖,互斥就是 AQS 中的排它鎖的意思,只允許一個(gè)線程獲得鎖。

簡(jiǎn)單應(yīng)用

ReentrantLock 的使用相比較 synchronized 會(huì)稍微繁瑣一點(diǎn),所謂顯示鎖,也就是你在代碼中需要主動(dòng)的去進(jìn)行 lock 操作。一般來講我們可以按照下面的方式使用 ReentrantLock

  1. Lock lock = new ReentrantLock(); 
  2. lock.lock(); 
  3. try { 
  4.   doSomething(); 
  5. }finally { 
  6.   lock.unlock(); 

lock.lock () 就是在顯式的上鎖。上鎖后,下面的代碼塊一定要放到 try 中,并且要結(jié)合 finally 代碼塊調(diào)用lock.unlock ()來釋放鎖,否則一定 doSomething 方法中出現(xiàn)任何異常,這個(gè)鎖將永遠(yuǎn)不會(huì)被釋放掉。

公平鎖和非公平鎖

synchronized 是非公平鎖,也就是說每當(dāng)鎖匙放的時(shí)候,所有等待鎖的線程并不會(huì)按照排隊(duì)順去依次獲得鎖,而是會(huì)再次去爭(zhēng)搶鎖。ReentrantLock 相比較而言更為靈活,它能夠支持公平和非公平鎖兩種形式。只需要在聲明的時(shí)候傳入 true。

  1. Lock lock = new ReentrantLock(true); 

而默認(rèn)的無參構(gòu)造方法則會(huì)創(chuàng)建非公平鎖。

tryLock方法

前面我們通過 lock.lock (); 來完成加鎖,此時(shí)加鎖操作是阻塞的,直到獲取鎖才會(huì)繼續(xù)向下進(jìn)行。ReentrantLock 其實(shí)還有更為靈活的枷鎖方式 tryLock。

tryLock 方法有兩個(gè)重載,第一個(gè)是無參數(shù)的 tryLock 方法,被調(diào)用后,該方法會(huì)立即返回獲取鎖的情況。獲取為 true,未能獲取為 false。我們的代碼中可以通過返回的結(jié)果進(jìn)行進(jìn)一步的處理。第二個(gè)是有參數(shù)的 tryLock 方法,通過傳入時(shí)間和單位,來控制等待獲取鎖的時(shí)長(zhǎng)。如果超過時(shí)間未能獲取鎖則放回 false,反之返回 true。使用方法如下:

  1. if(lock.tryLock(2, TimeUnit.SECONDS)){ 
  2.    try { 
  3.       doSomething(); 
  4.    } catch (InterruptedException e) { 
  5.       e.printStackTrace(); 
  6.    }finally { 
  7.       lock.unlock(); 
  8.    } 
  9. }else
  10.   doSomethingElse(); 

我們?nèi)绻幌M麩o法獲取鎖時(shí)一直等待,而是希望能夠去做一些其它事情時(shí),可以選擇此方式。

類結(jié)構(gòu)

ReentrantLock 類本身是不繼承 AQS 的,實(shí)現(xiàn)了 Lock 接口,如下:

  1. public class ReentrantLock implements Lock, java.io.Serializable {} 

Lock 接口定義了各種加鎖,釋放鎖的方法,接口有如下幾個(gè):

  1. // 獲得鎖方法,獲取不到鎖的線程會(huì)到同步隊(duì)列中阻塞排隊(duì) 
  2. void lock(); 
  3. // 獲取可中斷的鎖 
  4. void lockInterruptibly() throws InterruptedException; 
  5. // 嘗試獲得鎖,如果鎖空閑,立馬返回 true,否則返回 false 
  6. boolean tryLock(); 
  7. // 帶有超時(shí)等待時(shí)間的鎖,如果超時(shí)時(shí)間到了,仍然沒有獲得鎖,返回 false 
  8. boolean tryLock(long time, TimeUnit unit) throws InterruptedException; 
  9. // 釋放鎖 
  10. void unlock(); 
  11. // 得到新的 Condition 
  12. Condition newCondition(); 

ReentrantLock 就負(fù)責(zé)實(shí)現(xiàn)這些接口,我們使用時(shí),直接面對(duì)的也是這些方法,這些方法的底層實(shí)現(xiàn)都是交給 Sync 內(nèi)部類去實(shí)現(xiàn)的,Sync 類的定義如下:

  1. abstract static class Sync extends AbstractQueuedSynchronizer {} 

都是最終繼承自 AbstractQueuedSynchronizer。這就是著名的 AQS。通過查看 AQS 的注釋我們了解到, AQS 依賴先進(jìn)先出隊(duì)列實(shí)現(xiàn)了阻塞鎖和相關(guān)的同步器(信號(hào)量、事件等)。

AQS 內(nèi)部有一個(gè) volatile 類型的 state 屬性,實(shí)際上多線程對(duì)鎖的競(jìng)爭(zhēng)體現(xiàn)在對(duì) state 值寫入的競(jìng)爭(zhēng)。一旦 state 從 0 變?yōu)? 1,代表有線程已經(jīng)競(jìng)爭(zhēng)到鎖,那么其它線程則進(jìn)入等待隊(duì)列。

等待隊(duì)列是一個(gè)鏈表結(jié)構(gòu)的 FIFO 隊(duì)列,這能夠確保公平鎖的實(shí)現(xiàn)。同一線程多次獲取鎖時(shí),如果之前該線程已經(jīng)持有鎖,那么對(duì) state 再次加 1。釋放鎖時(shí),則會(huì)對(duì) state-1。直到減為 0,才意味著此線程真正釋放了鎖。

Sync 繼承了 AbstractQueuedSynchronizer ,所以 Sync 就具有了鎖的框架,根據(jù) AQS 的框架,Sync 只需要實(shí)現(xiàn) AQS 預(yù)留的幾個(gè)方法即可,但 Sync 也只是實(shí)現(xiàn)了部分方法,還有一些交給子類 NonfairSync 和 FairSync 去實(shí)現(xiàn)了,NonfairSync 是非公平鎖,F(xiàn)airSync 是公平鎖,定義如下:

  1. // 同步器 Sync 的兩個(gè)子類鎖 
  2. static final class FairSync extends Sync {} 
  3. static final class NonfairSync extends Sync {} 

幾個(gè)類整體的結(jié)構(gòu)如下:

圖中 Sync、NonfairSync、FairSync 都是靜態(tài)內(nèi)部類的方式實(shí)現(xiàn)的,這個(gè)也符合 AQS 框架定義的實(shí)現(xiàn)標(biāo)準(zhǔn)。

構(gòu)造器

ReentrantLock 構(gòu)造器有兩種,代碼如下:

  1. // 無參數(shù)構(gòu)造器,相當(dāng)于 ReentrantLock(false),默認(rèn)是非公平的 
  2. public ReentrantLock() { 
  3.     sync = new NonfairSync(); 
  4.   
  5. public ReentrantLock(boolean fair) { 
  6.     sync = fair ? new FairSync() : new NonfairSync(); 

無參構(gòu)造器默認(rèn)構(gòu)造是非公平的鎖,有參構(gòu)造器可以選擇。

從構(gòu)造器中可以看出,公平鎖是依靠 FairSync 實(shí)現(xiàn)的,非公平鎖是依靠 NonfairSync 實(shí)現(xiàn)的

源碼解析

Sync同步器

Sync 表示同步器,繼承了 AQS:

從圖中可以看出,lock 方法是個(gè)抽象方法,留給 FairSync 和 NonfairSync 兩個(gè)子類去實(shí)現(xiàn)。

加鎖方法

FairSync公平鎖

FairSync 公平鎖只實(shí)現(xiàn)了 lock 和 tryAcquire 兩個(gè)方法,lock 方法非常簡(jiǎn)單,如下:

  1. // acquire 是 AQS 的方法,表示先嘗試獲得鎖,失敗之后進(jìn)入同步隊(duì)列阻塞等待 
  2. final void lock() { 
  3.     acquire(1); 

在 FairSync 并沒有重寫 acquire 方法代碼。調(diào)用的為 AbstractQueuedSynchronizer 的代碼,如下:

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

首先調(diào)用一次 tryAcquire 方法。如果 tryAcquire 方法返回 true,那么 acquire 就會(huì)立即返回。

但如果 tryAcquire 返回了 false,那么則會(huì)先調(diào)用 addWaiter,把當(dāng)前線程包裝成一個(gè)等待的 node,加入到等待隊(duì)列。然后調(diào)用 acquireQueued 嘗試排隊(duì)獲取鎖,如果成功后發(fā)現(xiàn)自己被中斷過,那么返回 true,導(dǎo)致 selfInterrupt 被觸發(fā),這個(gè)方里只是調(diào)用Thread.currentThread ().interrupt ();進(jìn)行 interrupt。

acquireQueued 代碼如下:

在此方法中進(jìn)入自旋,不斷查看自己排隊(duì)的情況。如果輪到自己( header 是已經(jīng)獲取鎖的線程,而 header 后面的線程是排隊(duì)到要去獲取鎖的線程),那么調(diào)用 tryAcquire 方法去獲取鎖,然后把自己設(shè)置為隊(duì)列的 header。在自旋中,如果沒有排隊(duì)到自己,還會(huì)檢查是否應(yīng)該應(yīng)該被中斷。

整個(gè)獲取鎖的過程我們可以總結(jié)下:

直接通過 tryAcquire 嘗試獲取鎖,成功直接返回;

如果沒能獲取成功,那么把自己加入等待隊(duì)列;

自旋查看自己的排隊(duì)情況;

如果排隊(duì)輪到自己,那么嘗試通過 tryAcquire 獲取鎖;

如果沒輪到自己,那么回到第三步查看自己的排隊(duì)情況。

從以上過程我們可以看到鎖的獲取是通過 tryAcquire 方法。而這個(gè)方法在 FairSync 和 NonfairSync 有不同實(shí)現(xiàn)。

這個(gè)tryAcquire 方法是 AQS 在 acquire 方法中留給子類實(shí)現(xiàn)的抽象方法,F(xiàn)airSync 中實(shí)現(xiàn)的源碼如下:

實(shí)際上它的實(shí)現(xiàn)和 NonfairSync 的實(shí)現(xiàn),只是在 c==0 時(shí),多了對(duì) hasQueuedPredecessors 方法的調(diào)用。故名思義,這個(gè)方法做的事情就是判斷當(dāng)前線程是否前面還有排隊(duì)的線程。

當(dāng)它前面沒有排隊(duì)線程,說明已經(jīng)排隊(duì)到自己了,這是才會(huì)通過 CAS 的的方式去改變 state 值為 1,如果成功,那么說明當(dāng)前線程獲取鎖成功。接下來就是調(diào)用 setExclusiveOwnerThread 把自己設(shè)置成為鎖的擁有者。else if 中邏輯則是在處理重入邏輯,如果當(dāng)前線程就是鎖的擁有者,那么會(huì)把 state 加 1 更新回去。

通過以上分析,我們可以看出 AbstractQueuedSynchronizer 提供 acquire 方法的模板邏輯,但其中真正對(duì)鎖的獲取方法 tryAcquire,是在不同子類中實(shí)現(xiàn)的,這是很好的設(shè)計(jì)思想。

NonfairSync非公平鎖

NonfairSync 底層實(shí)現(xiàn)了 lock 和 tryAcquire 兩個(gè)方法,如下:

nonfairTryAcquire

以上代碼有三點(diǎn)需要注意:

通過判斷 AQS 的 state 的狀態(tài)來決定是否可以獲得鎖,0 表示鎖是空閑的;

else if 的代碼體現(xiàn)了可重入加鎖,同一個(gè)線程對(duì)共享資源重入加鎖,底層實(shí)現(xiàn)就是把 state + 1,并且可重入的次數(shù)是有限制的,為 Integer 的最大值;

這個(gè)方法是非公平的,所以只有非公平鎖才會(huì)用到,公平鎖是另外的實(shí)現(xiàn)。

無參的 tryLock 方法調(diào)用的就是此方法,tryLock 的方法源碼如下:

  1. public boolean tryLock() { 
  2.     // 入?yún)?shù)是 1 表示嘗試獲得一次鎖 
  3.     return sync.nonfairTryAcquire(1); 

其底層的調(diào)用關(guān)系(只是簡(jiǎn)單表明調(diào)用關(guān)系,并不是完整分支圖)如下:

解鎖方法

  1. public void unlock() { 
  2.     sync.release(1); 

和 lock 很像,實(shí)際調(diào)用的是 sync 實(shí)現(xiàn)類的 release 方法。和 lock 方法一樣,這個(gè) release 方法在 AbstractQueuedSynchronizer 中,

  1. if (tryRelease(arg)) { 
  2.     Node h = head; 
  3.     if (h != null && h.waitStatus != 0) 
  4.         unparkSuccessor(h); 
  5.     return true
  6. return false

這個(gè)方法中會(huì)先執(zhí)行 tryRelease,它的實(shí)現(xiàn)也在 AbstractQueuedSynchronizer 的子類 Sync 中,如果釋放鎖成功,那么則會(huì)通過 unparkSuccessor 方法找到隊(duì)列中第一個(gè) waitStatus<0的線程進(jìn)行喚醒。我們下面看一下 tryRelease 方法代碼:

tryRelease 方法是公平鎖和非公平鎖都公用的,在鎖釋放的時(shí)候,是沒有公平和非公平的說法的。

從代碼中可以看到,鎖最終被釋放的標(biāo)椎是 state 的狀態(tài)為 0,在重入加鎖的情況下,需要重入解鎖相應(yīng)的次數(shù)后,才能最終把鎖釋放,比如線程 A 對(duì)共享資源 B 重入加鎖 5 次,那么釋放鎖的話,也需要釋放 5 次之后,才算真正的釋放該共享資源了。

總結(jié)

本篇文章 ReentrantLock 的使用及其核心源代碼,其實(shí) Lock 相關(guān)的代碼還有很多。我們可以嘗試自己去閱讀。

ReentrantLock 的設(shè)計(jì)思想是通過 FIFO 的隊(duì)列保存等待鎖的線程。通過 volatile 類型的 state 保存鎖的持有數(shù)量,從而實(shí)現(xiàn)了鎖的可重入性。而公平鎖則是通過判斷自己是否排隊(duì)成功,來決定是否去爭(zhēng)搶鎖。

然后我們了解到AQS 搭建了整個(gè)鎖架構(gòu),子類鎖只需要根據(jù)場(chǎng)景,實(shí)現(xiàn) AQS 對(duì)應(yīng)的方法即可,不僅僅是 ReentrantLock 是這樣,JUC 中的其它鎖也都是這樣,只要對(duì) AQS 了如指掌,鎖其實(shí)非常簡(jiǎn)單。

本文轉(zhuǎn)載自微信公眾號(hào)「月伴飛魚」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系月伴飛魚公眾號(hào)。

 

責(zé)任編輯:武曉燕 來源: 日常加油站
相關(guān)推薦

2022-11-14 11:09:36

源碼AQS加鎖

2020-08-24 08:13:25

非公平鎖源碼

2020-09-16 07:43:44

Vue

2022-07-11 20:46:39

AQSJava

2023-09-12 13:48:47

2022-12-31 09:42:14

超時(shí)功能

2023-04-06 00:15:03

JavaReentrantL線程

2020-11-02 09:35:04

ReactHook

2020-12-03 08:14:45

Axios核心Promise

2019-08-05 13:20:35

Android繪制代碼

2022-12-26 00:00:04

公平鎖非公平鎖

2021-03-04 08:26:17

synchronizeReentrantLojava

2021-07-12 09:45:36

NameServer 核心Conusmer

2021-04-28 10:13:58

zookeeperZNode核心原理

2021-04-21 07:52:39

核心SignalR應(yīng)用

2021-10-12 09:46:00

Pipelineshell命令Jenkins

2021-08-02 07:57:03

注冊(cè)Nacos源碼

2020-05-21 13:25:43

Spring組件架構(gòu)

2023-05-08 14:56:00

Kafka高可靠高性能

2018-03-21 11:05:26

Spark大數(shù)據(jù)應(yīng)用程序
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)