ReentrantLock 非公平鎖實現(xiàn)原理詳解
正文
在線程間通信方式2一節(jié)中,我們了解了Lock,Condition和ReentrantLock,學習了簡單使用Condition和RentrantLock完成線程間通信,從文章中我們了解到ReentrantLock是Lock接口的一個最常用的實現(xiàn)類,ReentrantLock是獨占鎖,獨占鎖的場景下又支持公平鎖和非公平鎖,那么在源碼實現(xiàn)中,ReentrantLock繼承關系,實現(xiàn)結構又是怎樣的呢?
ReentrantLock繼承關系及關聯(lián)類
在多線程與鎖中,我們了解到ReentrantLock是支持公平鎖和非公平鎖的,對應的構造函數(shù)源碼如下:
// ReentrantLock.java public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }
可以看到如果是公平鎖則創(chuàng)建FairSync對象,如果是非公平鎖則創(chuàng)建NonfairSync,再結合Lock接口核心的lock,tryLock,unlock函數(shù)源碼(Lock接口在線程間通信方式2中有介紹)可以看出,在ReentrantLock中,使用FairSync或NonfairSync代理了鎖狀態(tài)管理,lock,tryLock和unlock實現(xiàn)源碼如下:
// ReentrantLock.java public void lock() { sync.lock(); } ? public boolean tryLock() { return sync.nonfairTryAcquire(1); } ? public void unlock() { sync.release(1); }
由此,進一步梳理ReentrantLock實現(xiàn),可以得到下圖:
上圖中一些生僻類及其作用見下表:
類名 | 說明 | 備注 |
---|---|---|
FairSync | ReentrantLock中公平鎖的實現(xiàn)類 | / |
NonfairSync | ReentrantLock中非公平鎖的實現(xiàn)類 | / |
Sync | 公平鎖和非公平鎖實現(xiàn)類的共同父類,使用AbstractQueuedSynchronizer狀態(tài)記錄鎖的持有數(shù)據(jù) | / |
AbstractQueuedSynchronizer | AbstractQueuedSynchronizer簡稱AQS,一個用于實現(xiàn)阻塞鎖和同步器的工具類,其內(nèi)部維護一個先進先出的等待隊列(真實數(shù)據(jù)結構是雙向鏈表),依賴一個int型的數(shù)據(jù)管理同步狀態(tài) | / |
AbstractOwnableSynchronizer | AQS的父類,定義了一個線程獨占的同步器,用于實現(xiàn)創(chuàng)建鎖和鎖占有標記的基礎類,其內(nèi)部使用exclusiveOwnerThread記錄當前占用鎖的線程 | / |
Serializable | 序列化接口 | / |
ReentrantLock.lock流程分析
跟蹤ReentrantLock.lock調(diào)用流程,可以得到下面的時序圖(以非公平鎖為例分析):
上圖中描述了ReentrantLock中l(wèi)ock在資源空和資源被占用的情況下的執(zhí)行流程,接下來我們來看下其內(nèi)部的細節(jié)實現(xiàn)。
ReentrantLock.lock獲取鎖成功
NonfairSynck類中的lock代碼如下所示:
// NonfairSync.java final void lock() { if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); }
可以看出,我們在調(diào)用了ReentrantLock.lock后,NonfairSync會首先嘗試通過CAS將資源占用狀態(tài)置為1(compareAndSetState,默認值0,期望值1),如果執(zhí)行成功,說明當前獲取共享資源成功,將當前線程設置為獨占鎖持有者,當前線程繼續(xù)執(zhí)行。
compareAndSetState
compareAndSetState操作的是AQS中聲明的一個int型的值,如果其值為0,表示當前鎖空閑,如果有線程到來可以占用鎖,如果值大于1,表示當前鎖被占用,為保證多線程對該值的操作實時可見,使用volatile修飾該變量,相關代碼如下:
// AbstractQueuedSynchronizer.java private volatile int state; protected final boolean compareAndSetState(int expect, int update) { // See below for intrinsics setup to support this return unsafe.compareAndSwapInt(this, stateOffset, expect, update); }
setExclusiveOwnerThread
setExclusiveOwnerThread最終是將當前線程設置到AbstractOwnableSynchronizer中定義的exclusiveOwnerThread中,代碼如下:
// AbstractOwnableSynchronizer.java private transient Thread exclusiveOwnerThread; ? protected final void setExclusiveOwnerThread(Thread thread) { exclusiveOwnerThread = thread; }
transient關鍵字:
transient關鍵字的主要作用是讓某些被transient關鍵字修飾的變量不被序列化,如果對transient修飾的變量執(zhí)行了序列化,則該變量會重新執(zhí)行默認初始化,反序列化得到的對象是null
ReentrantLock.lock獲取鎖失敗
前文中可以看到如果compareAndSetState執(zhí)行返回false的話,就說明當前共享資源被占用,隨后走else邏輯,執(zhí)行acquire(1),其內(nèi)容如下:
// NonfairSync.java final void lock() { if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); } ? // AbstractQueuedSynchronizer.java public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }
其內(nèi)部主要由兩塊組成,tryAcquire和acquireQueued,其中tryAcquire再次嘗試獲取鎖,如果獲取成功,則流程結束,當前線程正常繼續(xù)執(zhí)行,如果獲取失敗,則執(zhí)行acquireQueued方法(由于使用且操作符連接tryAcquire和acquireQueued,所以只有tryAcquire返回false的時候,才會執(zhí)行acquireQueued方法),該方法接受addWaiter返回的Node參數(shù),下面來詳細看下兩個函數(shù)的具體實現(xiàn)。
tryAcquire
tryAcquire在NonfairSync中的實現(xiàn)如下,其最終調(diào)用到的是Sync類的nonfairTryAcquire方法:
// NonfairSync.java protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); }
// Sync.java final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); // 獲取當前同步資源狀態(tài) int c = getState(); // 同步資源空閑 if (c == 0) { // 占用同步資源更新資源狀態(tài) if (compareAndSetState(0, acquires)) { // 設置當前線程為資源對應的獨占鎖持有者 setExclusiveOwnerThread(current); return true; } } // 如果當前線程就是持有鎖線程,更新鎖狀態(tài),直接持有鎖 else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }
可以看到在tryAcquire中實際上也是嘗試獲取鎖的過程,首先檢查當前當前同步資源狀態(tài),如果不可獲取,則檢查當前線程時否是持鎖線程,是的話則直接獲取鎖(可重入鎖的實現(xiàn)),更新同步資源狀態(tài),如果均失敗,則返回false。
acquireQueued
acquireQueued在AQS中關聯(lián)的核心代碼如下所示,可以看出主要包含addWaiter和acquireQueued兩部分:
addWaiter
// AbstractQueuedSynchronizer.java private Node enq(final Node node) { for (;;) { Node t = tail; if (t == null) { // Must initialize if (compareAndSetHead(new Node())) tail = head; } else { node.prev = t; if (compareAndSetTail(t, node)) { t.next = node; return t; } } } }
// AbstractQueuedSynchronizer.java private Node addWaiter(Node mode) { // 創(chuàng)建新的Node對象 Node node = new Node(Thread.currentThread(), mode); // Try the fast path of enq; backup to full enq on failure Node pred = tail; if (pred != null) { node.prev = pred; if (compareAndSetTail(pred, node)) { pred.next = node; return node; } } enq(node); return node; }
可以看到不管是addWaiter還是enq,其最終目標都是基于當前線程創(chuàng)建新的Node對象,將新的Node對象添加在隊尾,前文提到AQS中維護了一個先進先出的隊列,其數(shù)據(jù)結構本質(zhì)是雙向鏈表,這里的head,tail就是鏈表的具體實現(xiàn)。Node類中包含了指向前一個元素和后一個元素的引用,Node的聲明如下:
// Node實體類 static final class Node { ... // 前一個元素 volatile Node prev; // 下一個元素 volatile Node next; // 線程對象 volatile Thread thread; Node() { // Used to establish initial head or SHARED marker } Node(Thread thread, Node mode) { // Used by addWaiter this.nextWaiter = mode; this.thread = thread; } Node(Thread thread, int waitStatus) { // Used by Condition this.waitStatus = waitStatus; this.thread = thread; } ... } // AbstractQueuedSynchronizer.java中聲明Node對象 // 鏈表頭 private transient volatile Node head; // 鏈表尾部 private transient volatile Node tail;
acquireQueued
acquireQueued源碼如下:
// AbstractQueuedSynchronizer.java final boolean acquireQueued(final Node node, int arg) { boolean failed = true; try { boolean interrupted = false; for (;;) { final Node p = node.predecessor(); if (p == head && tryAcquire(arg)) { setHead(node); p.next = null; // help GC failed = false; return interrupted; } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); } }
可以看到在addWaiter中成功將新的Node添加到隊尾后,acquireQueued中當前線程會自旋,嘗試獲取鎖,如果獲取失敗,則執(zhí)行shouldParkAfterFailedAcquire和parkAndCheckInterrupt,這兩個函數(shù)都返回true則執(zhí)行selfInterrupt方法(代碼見acquire函數(shù)部分)。
shouldParkAfterFailedAcquire用于判斷是否應該對當前線程阻塞,如果是的話則返回true,parkAndCheckInterrupt用于執(zhí)行線程阻塞并且判斷當前線程是否處于interrupt狀態(tài),如果是則會返回true。通過源碼可以看到其是通過LockSupport.park進行線程狀態(tài)切換的,代碼如下:
// AbstractQueuedSynchronizer.java private final boolean parkAndCheckInterrupt() { LockSupport.park(this); return Thread.interrupted(); }
LockSupport.park作用
LockSupport.park用于在許可證不可用時阻塞禁用當前線程,如果許可證可用,則該許可證被消耗,并且當前調(diào)用立即返回,否則,出于線程調(diào)度的目的,當前線程將被阻塞禁用,并進入休眠狀態(tài),直到發(fā)生以下三種情況之一:
- 其他線程以當前線程為參數(shù)調(diào)用unpark
- 其他線程中斷當前線程
- 調(diào)用發(fā)生異常
結合上文,我們可以得出ReentrantLock.lock執(zhí)行的一般流程如下所示:
ReentrantLock.unlock流程分析
跟蹤ReentrantLock.unlock調(diào)用流程,可以得到下面的時序圖(以非公平鎖為例分析):
可以看到對于ReentrantLock.unlock流程而言,其核心實現(xiàn)函數(shù)是tryRelease和unparkSuccessor,release函數(shù)如下所示:
// ReentrantLock.java public void unlock() { sync.release(1); } // AQS.java 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執(zhí)行完成返回true后,我們會獲取鏈表首位元素,當首位元素不為空且等待狀態(tài)(waitStatus)不等于0時,執(zhí)行unparkSuccessor,在這里我們又遇到了Node類的另一個核心元素waitStatus,其聲明如下:
static final class Node { ... // waitStatus的四種可能取值,表示當前節(jié)點及其后續(xù)節(jié)點對應線程的運行狀態(tài) static final int CANCELLED = 1; static final int SIGNAL = -1; static final int CONDITION = -2; static final int PROPAGATE = -3; // 聲明waitStatus volatile int waitStatus; ... }
四種取值含義如下所示:
- SIGNAL:取值為-1,該節(jié)點的后續(xù)節(jié)點處于被阻塞或即將阻塞的狀態(tài),因此當前節(jié)點的線程在釋放鎖或取消時必須解除后繼節(jié)點的阻塞狀態(tài),使后續(xù)節(jié)點的線程得以正常運行
- CANCELLED:取值為1,當前節(jié)點對應的線程由于超時或中斷而被取消,Node的waitStatus取該值,進入取消狀態(tài)后節(jié)點狀態(tài)不再變化
- CONDITION:取值為-2,該節(jié)點當前在等待隊列中,節(jié)點對應的線程等待Condition,當其他線程對Condition調(diào)用了signal方法后,該節(jié)點從等待隊列中轉(zhuǎn)入鏈表中,進行同步狀態(tài)的獲取
- PROPAGATE:取值為-3,在共享鎖實現(xiàn)中使用,當前節(jié)點線程處于可運行狀態(tài)
為了簡化使用,對于waitStatus取值并沒有按照數(shù)字遞增或遞減排列進行取值,如果該節(jié)點取值為非負值,則代表不需要將操作同步到其他節(jié)點,對于普通Node節(jié)點而言,waitStatus字段初始化為0,對于條件節(jié)點,該字段初始化為1,在代碼中使用CAS對該字段進行修改。
下面我們來分別看下這兩個核心函數(shù)的實現(xiàn)
tryRelease
tryRelease主要用于對鎖狀態(tài)標記進行清理,函數(shù)實現(xiàn)如下所示:
protected final boolean tryRelease(int releases) { // 獲取AQS的鎖狀態(tài)標記,計算剩余鎖狀態(tài)標記 int c = getState() - releases; if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; // 鎖狀態(tài)標記為0,當前沒有其他線程持有鎖,鎖處于空閑狀態(tài),設置鎖持有者為null if (c == 0) { free = true; setExclusiveOwnerThread(null); } // 更新鎖狀態(tài) setState(c); return free; }
在ReentrantLock獨占鎖實現(xiàn)的場景下,state鎖狀態(tài)標記取值只有兩個,0和1(前文lock過程中也有分析通過CAS改變state狀態(tài)時,一直是從0到1的修改),進而當當前線程執(zhí)行tryRelease后,state鎖狀態(tài)標記取值更新為0,表示當前鎖處于空閑狀態(tài),隨后自然要喚醒Node鏈表中的其他節(jié)點去獲取鎖啦。
unparkSuccessor
unparkSuccessor函數(shù)主要用于更新Node節(jié)點的waitStatus狀態(tài)并按需將Node鏈表中阻塞的線程喚醒執(zhí)行,實現(xiàn)如下所示:
private void unparkSuccessor(Node node) { // 獲取頭節(jié)點的waitStatus狀態(tài),將頭節(jié)點的waitStatus狀態(tài)設置為0,head waitStatus恢復初始狀態(tài) int ws = node.waitStatus; if (ws < 0) compareAndSetWaitStatus(node, ws, 0); // 獲取頭節(jié)點的下一個節(jié)點,如果下一個節(jié)點為null或者狀態(tài)為取消狀態(tài)(waitStatus大于0只有取消狀態(tài)),則從尾節(jié)點開始遍歷,查找未被取消的后續(xù)節(jié)點對象 Node s = node.next; if (s == null || s.waitStatus > 0) { s = null; for (Node t = tail; t != null && t != node; t = t.prev) if (t.waitStatus <= 0) s = t; } // 對上一步獲取到的節(jié)點對應的線程執(zhí)行unpark喚醒,去搶占鎖 if (s != null) LockSupport.unpark(s.thread); }
從前面可以看出這里傳入的node是當前的head(鏈表頭),判斷當前頭節(jié)點的waitStatus,如果不是初始值則重置為初始值,隨著查找下一個要被喚醒的節(jié)點,查找到后,喚醒節(jié)點對應的線程,讓線程去嘗試搶占鎖。
結合上文,我們可以得出ReentrantLock.unlock執(zhí)行的一般流程如下所示:
以上就是ReentrantLock 非公平鎖實現(xiàn)原理詳解的詳細內(nèi)容,更多關于ReentrantLock 非公平鎖的資料請關注腳本之家其它相關文章!
相關文章
JavaWeb請求轉(zhuǎn)發(fā)和請求包含實現(xiàn)過程解析
這篇文章主要介紹了JavaWeb請求轉(zhuǎn)發(fā)和請求包含實現(xiàn)過程解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2020-02-02解決異常處理問題:getReader()?has?already?been?called?for?this
這篇文章主要介紹了解決異常處理:getReader()?has?already?been?called?for?this問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-01-01SpringCloudConfig之client端報錯Could?not?resolve?placeholder問
這篇文章主要介紹了SpringCloudConfig之client端報錯Could?not?resolve?placeholder?‘from‘?in?value?“${from}“問題及解決方案,具有很好的參考價值,希望對大家有所幫助2022-12-12關于java中@Async異步調(diào)用詳細解析附代碼
本文主要介紹了java關于@Async異步調(diào)用詳細解析附代碼,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2022-07-07