文章目錄
- 3.深入理解AQS、ReentrantLock
- 3.1AQS
- 3.1.1AQS簡介
- 3.1.2核心結構
- (1)設計模型
- (2)組成部分
- (3)State關鍵字
- 3.1.3實現的兩類隊列
- (1)同步隊列
- ①CLH
- ②Node
- ③主要行為
- img條件隊列
- 3.1.4總結
- 3.2ReentrantLock
- 3.2.1Synchronized和ReentrantLock
- (1)性能上的比較
- (2)獲取公平鎖
- (3)綜述
- 3.2.2可重入功能的實現原理
- 3.2.3非公平鎖的實現原理
- (1)加鎖
- (2)釋放鎖
- 3.2.4公平鎖的實現原理
- 3.2.4tryLock原理
- 3.2.5可中斷的獲取鎖
- 3.2.6可超時的獲取鎖
- 3.2.7小結
3.深入理解AQS、ReentrantLock
3.1AQS
3.1.1AQS簡介
AQS即隊列同步器AbstractQueuedSynchronizer(后面簡稱AQS)是實現鎖和有關同步器的一個基礎框架。
在JDK5中,Doug Lea在并發包中加入了大量的同步工具,例如==重入鎖(ReentrantLock)、讀寫鎖(ReentrantReadWriteLock)、信號量(Semaphore)、CountDownLatch(倒計時鎖)==等,都是基于AQS的。
其內部通過一個被標識為volatile的名為state的變量來控制多個線程之間的同步狀態。多個線程之間可以通過AQS來獨占式或共享式的搶占資源。
基于AQS,可以很方便的實現Java中不具備的功能。
例如,在鎖這個問題上,Java中提供的是synchronized關鍵字,用這個關鍵字可以很方便的實現多個線程之間的同步。但這個關鍵字也有很多缺陷,比如:
- 不支持超時的獲取鎖:一個線程一旦沒有從synchronized上獲取鎖,就會卡在這里,沒有機會逃脫。所以通常由synchronized造成的死鎖是無解的。
- 不可響應中斷。
- 不能嘗試獲取鎖。如果嘗試獲取時沒獲取到,立刻返回,synchronized不具備這一特性。
而ReentrantLock基于AQS將上述幾點都做到了。
3.1.2核心結構
從AbstractQueuedSynchronizer的名字可以看出,AQS中一定是基于隊列實現的(Queue)。
在AQS內部,是通過鏈表實現的隊列。
鏈表的每個元素是其內部類Node的一個實現。然后AQS通過實例變量head指向隊列的頭,通過實例變量tail指向隊列的尾。
其源碼定義如下:
/*** Head of the wait queue, lazily initialized. Except for* initialization, it is modified only via method setHead. Note:* If head exists, its waitStatus is guaranteed not to be* CANCELLED.*/
private transient volatile Node head;/*** Tail of the wait queue, lazily initialized. Modified only via* method enq to add new wait node.*/
private transient volatile Node tail;/*** The synchronization state.*/
private volatile int state;static final class Node {/** 標識為共享式 */static final Node SHARED = new Node();/** 標識為獨占式 */static final Node EXCLUSIVE = null;/** 同步隊列中等待的線程等待超時或被中斷,需要從等待隊列中取消等待,進入該狀態的節點狀態將不再變化 */static final int CANCELLED = 1;/** 當前節點的后繼節點處于等待狀態,且當前節點釋放了同步狀態,需要通過unpark喚醒后繼節點,讓其繼續運行 */static final int SIGNAL = -1;/** 當前節點等待在某一Condition上,當其他線程調用這個Conditino的signal方法后,該節點將從等待隊列恢復到同步隊列中,使其有機會獲取同步狀態 */static final int CONDITION = -2;/** 表示下一次共享式同步狀態獲取狀態將無條件的傳播下去 */static final int PROPAGATE = -3;/* 當前節點的等待狀態,取值為上述幾個常量之一,另外,值為0表示初始狀態 */volatile int waitStatus;/* 前驅節點 */volatile Node prev;/* 后繼節點 */volatile Node next;/* 等待獲取同步狀態的線程 */volatile Thread thread;/* 等待隊列中的后繼節點 */Node nextWaiter;// ...
}
(1)設計模型
(2)組成部分
AQS 主要由三部分組成
- state 同步狀態
- Node 組成的 CLH 隊列
- ConditionObject 條件變量(包含 Node 組成的條件單向隊列)。
state 用 volatile 來修飾,保證了我們操作的可見性,所以任何線程通過 getState() 獲得狀態都是可以得到最新值,但是 setState() 無法保證原子性,因此 AQS 給我們提供了 compareAndSetState 方法利用底層 UnSafe 的 CAS 功能來實現原子性。
(3)State關鍵字
對于 AQS 來說,線程同步的關鍵是對 state 的操作,可以說獲取、釋放資源是否成功都是由 state 決定的,比如 state>0 代表可獲取資源,否則無法獲取,所以 state 的具體語義由實現者去定義,現有的 ReentrantLock、ReentrantReadWriteLock、Semaphore、CountDownLatch 定義的 state 語義都不一樣。
- ReentrantLock 的 state 用來表示是否有鎖資源,變量記錄了鎖的重入次數
- ReentrantReadWriteLock 的 state 高 16 位代表讀鎖狀態,低 16 位代表寫鎖狀態
- Semaphore 的 state 用來表示可用信號的個數
- CountDownLatch 的 state 用來表示計數器的值
3.1.3實現的兩類隊列
- 同步隊列:服務于線程阻塞等待獲取資源
- 條件隊列:服務于線程因某個條件不滿足而進入等待狀態。 條件隊列中的線程實際上已經獲取到了資源,但是沒有能夠繼續執行下去的條件,所以被打入條件隊列并釋放持有的資源,以讓渡其它線程執行,如果未來某個時刻條件得以滿足,則該線程會被從條件隊列轉移到同步隊列,繼續參與競爭資源,以繼續向下執行。
(1)同步隊列
①CLH
同步隊列是基于鏈表實現的雙向隊列,也是 CLH 鎖的變種。CLH 鎖是 AQS 隊列同步器實現的基礎。
以下圖為CLH的構成:
- CLH 鎖是有由 Craig, Landin, and Hagersten 這三個人發明的鎖,取了三個人名字的首字母,所以叫 CLH Lock。
- CLH 鎖是一個自旋鎖。能確保無饑餓性。提供先來先服務的公平性。
- CLH 隊列鎖也是一種基于鏈表的可擴展、高性能、公平的自旋鎖,申請線程僅僅在本地變量上自旋,它不斷輪詢前驅的狀態,假設發現前驅釋放了鎖就結束自旋。
②Node
AQS 以內部類 Node
的形式定義了同步隊列結點。這就是前文看到的第一個內部類。
static final class Node {/** 模式定義 */static final Node SHARED = new Node();static final Node EXCLUSIVE = null;/** 線程狀態 */static final int CANCELLED = 1;static final int SIGNAL = -1;static final int CONDITION = -2;static final int PROPAGATE = -3;/** 線程等待狀態 */volatile int waitStatus;/** 前驅結點 */volatile Node prev;/** 后置結點 */volatile Node next;/** 持有的線程對象 */volatile Thread thread;/** 對于獨占模式而言,指向下一個處于 CONDITION 等待狀態的結點;對于共享模式而言,則為 SHARED 結點 */Node nextWaiter;// ... 省略方法定義
}
Node 在 CLH 的基礎上進行了變種。
CLH 是單向隊列,其主要特點是自旋檢查前驅節點的 locked 狀態。
而 AQS 同步隊列是 雙向隊列,每個節點也有狀態 waitStatus,而其并不是一直對前驅節點的狀態自旋判斷,而是自旋一段時間后阻塞讓出 cpu 時間片(上下文切換),等待前驅節點主動喚醒后繼節點。
waitStatus 有如下 5 中狀態:
- CANCELLED = 1 表示當前結點已取消調度。當超時或被中斷(響應中斷的情況下),會觸發變更為此狀態,進入該狀態后的結點將不會再變化。
- SIGNAL = -1 表示后繼結點在等待當前結點喚醒。后繼結點入隊時,會將前繼結點的狀態更新為 SIGNAL。
- CONDITION = -2 表示結點等待在 Condition 上,當其他線程調用了 Condition 的 signal() 方法后,CONDITION 狀態的結點將從等待隊列轉移到同步隊列中,等待獲取同步鎖。
- PROPAGATE = -3 共享模式下,前繼結點不僅會喚醒其后繼結點,同時也可能會喚醒后繼的后繼結點。
- INITIAL = 0 新結點入隊時的默認狀態。
從上面的代碼中可以看出,位于 CLH 鏈表中的線程以 2 種模式在等待資源,即 SHARED 和 EXCLUSIVE,其中 SHARED 表示共享模式,而 EXCLUSIVE 表示獨占模式。
共享模式與獨占模式的主要區別在于,同一時刻獨占模式只能有一個線程獲取到資源,而共享模式在同一時刻可以有多個線程獲取到資源。典型的場景就是讀寫鎖,讀操作可以有多個線程同時獲取到讀鎖資源,而寫操作同一時刻只能有一個線程獲取到寫鎖資源,其它線程在嘗試獲取資源時都會被阻塞。
③主要行為
AQS 類成員變量 head 和 tail 字段分別指向同步隊列的頭結點和尾結點:
/*** Head of the wait queue, lazily initialized. Except for* initialization, it is modified only via method setHead. Note:* If head exists, its waitStatus is guaranteed not to be* CANCELLED.*/private transient volatile Node head;/*** Tail of the wait queue, lazily initialized. Modified only via* method enq to add new wait node.*/private transient volatile Node tail;
其中 head 表示同步隊列的頭結點,而 tail 則表示同步隊列的尾結點,具體組織形式如下圖:
當調用 AQS 的 acquire 方法獲取資源時,如果資源不足則當前線程會被封裝成 Node 結點添加到同步隊列的末端(入隊),頭結點 head 用于記錄當前正在持有資源的線程結點,而 head 的后繼結點就是下一個將要被調度的線程結點,當 release 方法被調用時,該結點上的線程將被喚醒(出隊),繼續獲取資源。
同步隊列的主要行為是 :入隊、出隊
- 入隊
獲取資源失敗的線程需要封裝成 Node 節點,接著尾部入隊,在 AQS 中提供 addWaiter 函數完成 Node 節點的創建與入隊。添加節點的時候,如 CLH 隊列已經存在,通過 CAS 快速將當前節點添加到隊列尾部,如果添加失敗或隊列不存在,則初始化同步隊列。
/*** Creates and enqueues node for current thread and given mode.** @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared* @return the new node*/
private Node addWaiter(Node mode) {Node node = new Node(mode);for (;;) {Node oldTail = tail;if (oldTail != null) {node.setPrevRelaxed(oldTail);if (compareAndSetTail(oldTail, node)) {oldTail.next = node;return node;}} else {initializeSyncQueue();}}
}
總結:線程獲取鎖失敗,入隊列,將新節點加到 tail 后面,然后對 tail 進行 CAS 操作,將 tail 指針后移到新節點上。
- 出隊
CLH 隊列中的節點都是獲取資源失敗的線程節點,當持有資源的線程釋放資源時,會將 head.next 指向的線程節點喚醒(CLH 隊列的第二個節點),如果喚醒的線程節點獲取資源成功,線程節點清空信息設置為頭部節點(新哨兵節點),原頭部節點出隊(原哨兵節點)
protected final boolean tryRelease(int releases) {int c = getState() - releases; if (Thread.currentThread() != getExclusiveOwnerThread())throw new IllegalMonitorStateException();boolean free = false;if (c == 0) { // 如果 state=0 了,就是可以釋放鎖了free = true; setExclusiveOwnerThread(null); // 將拿鎖線程置為 null}setState(c); // 重置同步器的 statereturn free; // 返回是否成功釋放}private void unparkSuccessor(Node node) {// node 節點是當前釋放鎖的節點,也是同步隊列的頭節點int ws = node.waitStatus;// 如果節點已經被取消了,把節點的狀態置為初始化if (ws < 0)compareAndSetWaitStatus(node, ws, 0);// 拿出隊二 sNode s = node.next;// s 為空,表示 node 的后一個節點為空// s.waitStatus 大于 0,代表 s 節點已經被取消了// 遇到以上這兩種情況,就從隊尾開始,向前遍歷,找到第一個 waitStatus 字段不是被取消的if (s == null || s.waitStatus > 0) {s = null;// 結束條件是前置節點就是 head 了for (Node t = tail; t != null && t != node; t = t.prev)// t.waitStatus <= 0 說明 t 當前沒有被取消,肯定還在等待被喚醒if (t.waitStatus <= 0)s = t;}// 喚醒以上代碼找到的線程if (s != null)LockSupport.unpark(s.thread);
}
總結:出隊列,鎖釋放喚醒 head 的后繼節點,head 的后繼節點從阻塞中醒來,開始搶鎖,獲取鎖成功,此時 head 指針向后移一個位置,原先 head 的后繼節點成為新的 head。
(2)條件隊列
一個 AQS 可以對應多個條件變量
ConditionObject 內部維護著一個單向條件隊列,不同于 CLH 隊列,條件隊列只入隊執行 await 的線程節點,并且加入條件隊列的節點,不能在 CLH 隊列, 條件隊列出隊的節點,會入隊到 CLH 隊列。
當某個線程執行了 ConditionObject 的 await 函數,阻塞當前線程,線程會被封裝成 Node 節點添加到條件隊列的末端,其他線程執行 ConditionObject 的 signal 函數,會將條件隊列頭部線程節點轉移到 CLH 隊列參與競爭資源,具體流程如下圖:
一個 Condition 對象就有一個單項的等待任務隊列。在一個多線程任務中我們可以 new 出多個等待任務隊列。比如我們 new 出來兩個等待隊列。
private Lock lock = new ReentrantLock();private Condition FirstCond = lock.newCondition();private Condition SecondCond = lock.newCondition();
所以真正的 AQS 任務中一般是一個任務隊列 N 個等待隊列的,因此我們盡量調用 signal 而少用 signalAll,因為在指定的實例化等待隊列中只有一個可以拿到鎖的。
3.1.4總結
- 狀態管理:AQS 內部維護了一個狀態變量state,通過該狀態變量來表示共享資源的狀態,可以是獨占模式也可以是共享模式。
- CAS 操作:AQS 使用 CAS(Compare And Swap)操作來實現對狀態變量的原子性修改,確保線程安全性。
- 線程阻塞和喚醒:當一個線程嘗試獲取鎖或訪問資源時,如果資源已被其他線程占用,則會將該線程阻塞并加入同步等待隊列,直到資源可用時再喚醒線程。
- 雙向鏈表:AQS 使用雙向鏈表來管理等待線程,保持線程之間的先后順序,即按照先進先出的原則進行訪問。
- 模板方法設計模式:AQS 提供了模板方法,允許子類通過實現特定的方法來控制鎖的獲取和釋放過程,從而實現不同類型的同步器。
3.2ReentrantLock
ReentrantLock,重入鎖,是JDK5中添加在并發包下的一個高性能的工具。
顧名思義,ReentrantLock支持同一個線程在未釋放鎖的情況下重復獲取鎖。
3.2.1Synchronized和ReentrantLock
(1)性能上的比較
首先,ReentrantLock的性能要優于synchronized。下面通過兩段代碼比價一下。 首先是synchronized:
PS:當存在大量線程競爭鎖時,多數情況下ReentrantLock的性能優于synchronized。
因為在JDK6中對synchronized做了優化
在鎖競爭不激烈的時候,多數情況下鎖會停留在偏向鎖和輕量級鎖階段,這兩個階段性能是很好的。
當存在大量競爭時,可能會膨脹為重量級鎖,性能下降,此時的ReentrantLock應該是優于synchronized的。
(2)獲取公平鎖
公平性是啥概念呢?如果是公平的獲取鎖,就是說多個線程之間獲取鎖的時候要排隊,依次獲取鎖;如果是不公平的獲取鎖,就是說多個線程獲取鎖的時候一哄而上,誰搶到是誰的。
由于synchronized是基于monitor機制實現的,它只支持非公平鎖;
但ReentrantLock同時支持公平鎖和非公平鎖。
(3)綜述
ReentrantLock還有一些其他synchronized不具備的特性,這里來總結一下。
3.2.2可重入功能的實現原理
ReentrantLock的實現基于隊列同步器(AbstractQueuedSynchronizer)后面簡稱AQS
ReentrantLock的可重入功能基于AQS的同步狀態:state。
核心原理:當某一線程獲取鎖后,將state值+1,并記錄下當前持有鎖的線程,
再有線程來獲取鎖時,判斷這個線程與持有鎖的線程是否是同一個線程,如果是,將state值再+1,如果不是,阻塞線程。 當線程釋放鎖時,將state值-1
當state值減為0時,表示當前線程徹底釋放了鎖,然后將記錄當前持有鎖的線程的那個字段設置為null,并喚醒其他線程,使其重新競爭鎖。
// acquires的值是1
final boolean nonfairTryAcquire(int acquires) {// 獲取當前線程final Thread current = Thread.currentThread();// 獲取state的值int c = getState();// 如果state的值等于0,表示當前沒有線程持有鎖// 嘗試將state的值改為1,如果修改成功,則成功獲取鎖,并設置當前線程為持有鎖的線程,返回trueif (c == 0) {if (compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}}// state的值不等于0,表示已經有其他線程持有鎖// 判斷當前線程是否等于持有鎖的線程,如果等于,將state的值+1,并設置到state上,獲取鎖成功,返回true// 如果不是當前線程,獲取鎖失敗,返回falseelse if (current == getExclusiveOwnerThread()) {int nextc = c + acquires;if (nextc < 0) // overflowthrow new Error("Maximum lock count exceeded");setState(nextc);return true;}return false;
}
3.2.3非公平鎖的實現原理
ReentrantLock有兩個構造函數:
// 無參構造,默認使用非公平鎖(NonfairSync)
public ReentrantLock() {sync = new NonfairSync();
}// 通過fair參數指定使用公平鎖(FairSync)還是非公平鎖(NonfairSync)
public ReentrantLock(boolean fair) {sync = fair ? new FairSync() : new NonfairSync();
}
sync是ReentrantLock的成員變量,是其內部類Sync的實例。NonfairSync和FairSync都是Sync類的子類。可以參考如下類關系圖:
Sync繼承了AQS,所以他具備了AQS的功能。同樣的,NonfairSync和FairSync都是AQS的子類。
當我們通過無參構造函數獲取ReentrantLock實例后,默認用的就是非公平鎖。
(1)加鎖
下面將通過如下場景描述非公平鎖的實現原理:假設一個線程(t1)獲取到了鎖,其他很多沒獲取到鎖的線程(others_t)加入到了AQS的同步隊列中等待,當這個線程執行完,釋放鎖后,其他線程重新非公平的競爭鎖。
注意:新來的線程執行lock方法時,都要嘗試下能不能直接獲取鎖,若獲取鎖成功,則記錄當前線程,否則調用AQS的acqurire方法,進入到同步隊列中等待
final void lock() {// 線程t1成功的將state的值從0改為1,表示獲取鎖成功// 并記錄當前持有鎖的線程if (compareAndSetState(0, 1))setExclusiveOwnerThread(Thread.currentThread());else// others_t線程們沒有獲取到鎖acquire(1);
}
如果獲取鎖失敗,會調用AQS的acquire方法:
public final void acquire(int arg) {// tryAcquire是個模板方法,在NonfairSync中實現,如果在tryAcquire方法中依然獲取鎖失敗,會將當前線程加入同步隊列中等待(addWaiter)if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();
}
tryAcquire的實現如下,其實是調用了上面的nonfairTryAcquire方法
protected final boolean tryAcquire(int acquires) {return nonfairTryAcquire(acquires);
}
(2)釋放鎖
OK,此時t1獲取到了鎖,others_t線程們都跑到同步隊列里等著了。
某一時刻,t1自己的任務執行完成,調用了釋放鎖的方法(unlock)。
public void unlock() {// 調用AQS的release方法釋放資源sync.release(1);
}
public final boolean release(int arg) {// tryRelease也是模板方法,在Sync中實現if (tryRelease(arg)) {Node h = head;if (h != null && h.waitStatus != 0)// 成功釋放鎖后,喚醒同步隊列中的下一個節點,使之可以重新競爭鎖// 注意此時不會喚醒隊列第一個節點之后的節點,這些節點此時還是無法競爭鎖unparkSuccessor(h);return true;}return false;
}
protected final boolean tryRelease(int releases) {// 將state的值-1,如果-1之后等于0,釋放鎖成功int c = getState() - releases;if (Thread.currentThread() != getExclusiveOwnerThread())throw new IllegalMonitorStateException();boolean free = false;if (c == 0) {free = true;setExclusiveOwnerThread(null);}setState(c);return free;
}
這時鎖被釋放了,被喚醒的線程(一個)和新來的線程重新競爭鎖(不包含同步隊列后面的那些線程)。
回到lock方法中,由于此時所有線程都能通過CAS來獲取鎖,并不能保證被喚醒的那個線程能競爭過新來的線程,所以是非公平的。這就是非公平鎖的實現。
這個過程大概可以描述為下圖這樣子:
3.2.4公平鎖的實現原理
公平鎖與非公平鎖的釋放鎖的邏輯是一樣的,都是調用上述的unlock方法,最大區別在于獲取鎖的時候:直接調用的AQS的acquire方法,沒有先嘗試獲取鎖。
static final class FairSync extends Sync {private static final long serialVersionUID = -3000897897090466540L;// 獲取鎖,與非公平鎖的不同的地方在于,這里直接調用的AQS的acquire方法,沒有先嘗試獲取鎖// acquire又調用了下面的tryAcquire方法,核心在于這個方法final void lock() {acquire(1);}/*** 這個方法和nonfairTryAcquire方法只有一點不同,在標注為#1的地方* 多了一個判斷hasQueuedPredecessors,這個方法是判斷當前AQS的同步隊列中是否還有等待的線程* 如果有,返回true,否則返回false。* 由此可知,當隊列中沒有等待的線程時,當前線程才能嘗試通過CAS的方式獲取鎖。* 否則就讓這個線程去隊列后面排隊。*/protected final boolean tryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();if (c == 0) {// #1if (!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);return true;}return false;}
}
通過注釋可知,在公平鎖的機制下,任何線程想要獲取鎖,都要排隊,不可能出現插隊的情況。這就是公平鎖的實現原理。
這個過程大概可以描述為下圖這樣子:
3.2.4tryLock原理
tryLock做的事情很簡單:讓當前線程嘗試獲取一次鎖,成功的話返回true,否則false。
其實就是調用了nonfairTryAcquire方法來獲取鎖。
public boolean tryLock() {return sync.nonfairTryAcquire(1);
}
至于獲取失敗的話,他也不會將自己添加到同步隊列中等待,直接返回false,讓業務調用代碼自己處理。
3.2.5可中斷的獲取鎖
中斷,也就是通過Thread的interrupt方法將某個線程中斷,中斷一個阻塞狀態的線程,會拋出一個InterruptedException異常。
如果獲取鎖是可中斷的,當一個線程長時間獲取不到鎖時,我們可以主動將其中斷,可避免死鎖的產生。
其實現方式如下:
public void lockInterruptibly() throws InterruptedException {sync.acquireInterruptibly(1);
}
會調用AQS的acquireInterruptibly方法:
public final void acquireInterruptibly(int arg)throws InterruptedException {// 判斷當前線程是否已經中斷,如果已中斷,拋出InterruptedException異常if (Thread.interrupted())throw new InterruptedException();if (!tryAcquire(arg))doAcquireInterruptibly(arg);
}
此時會優先通過tryAcquire嘗試獲取鎖,如果獲取失敗,會將自己加入到隊列中等待,并可隨時響應中斷。
private void doAcquireInterruptibly(int arg)throws InterruptedException {// 將自己添加到隊列中等待final Node node = addWaiter(Node.EXCLUSIVE);boolean failed = true;try {// 自旋的獲取鎖for (;;) {final Node p = node.predecessor();if (p == head && tryAcquire(arg)) {setHead(node);p.next = null; // help GCfailed = false;return;}// 獲取鎖失敗,在parkAndCheckInterrupt方法中,通過LockSupport.park()阻塞當前線程,// 并調用Thread.interrupted()判斷當前線程是否已經被中斷// 如果被中斷,直接拋出InterruptedException異常,退出鎖的競爭隊列if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())// #1throw new InterruptedException();}} finally {if (failed)cancelAcquire(node);}
}
PS:不可中斷的方式下,代碼#1位置不會拋出InterruptedException異常,只是簡單的記錄一下當前線程被中斷了。
3.2.6可超時的獲取鎖
通過如下方法實現,timeout是超時時間,unit代表時間的單位(毫秒、秒…)
public boolean tryLock(long timeout, TimeUnit unit)throws InterruptedException {return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
可以發現,這也是一個可以響應中斷的方法。然后調用AQS的tryAcquireNanos方法:
public final boolean tryAcquireNanos(int arg, long nanosTimeout)throws InterruptedException {if (Thread.interrupted())throw new InterruptedException();return tryAcquire(arg) ||doAcquireNanos(arg, nanosTimeout);
}
doAcquireNanos方法與中斷里面的方法大同小異,下面在注釋中說明一下不同的地方:
private boolean doAcquireNanos(int arg, long nanosTimeout)throws InterruptedException {if (nanosTimeout <= 0L)return false;// 計算超時截止時間final long deadline = System.nanoTime() + nanosTimeout;final Node node = addWaiter(Node.EXCLUSIVE);boolean failed = true;try {for (;;) {final Node p = node.predecessor();if (p == head && tryAcquire(arg)) {setHead(node);p.next = null; // help GCfailed = false;return true;}// 計算到截止時間的剩余時間nanosTimeout = deadline - System.nanoTime();if (nanosTimeout <= 0L) // 超時了,獲取失敗return false;// 超時時間大于1000納秒時,才阻塞// 因為如果小于1000納秒,基本可以認為超時了(系統調用的時間可能都比這個長)if (shouldParkAfterFailedAcquire(p, node) &&nanosTimeout > spinForTimeoutThreshold)LockSupport.parkNanos(this, nanosTimeout);// 響應中斷if (Thread.interrupted())throw new InterruptedException();}} finally {if (failed)cancelAcquire(node);}
}
3.2.7小結
本文首先對比了元老級的鎖synchronized與ReentrantLock的不同,ReentrantLock具有一下優勢: 同時支持公平鎖與非公平鎖 支持:嘗試非阻塞的一次性獲取鎖 支持超時獲取鎖 支持可中斷的獲取鎖 * 支持更多的等待條件(Condition)
然后介紹了幾個主要特性的實現原理,這些都是基于AQS的。
- 首先,ReentrantLock 采用了獨占模式,即同一時刻只允許一個線程持有鎖。這保證了被鎖保護的臨界區只能被一個線程訪問,從而避免了多個線程同時修改共享資源導致的數據競爭和不一致性。
- ReentrantLock的核心,是通過修改AQS中state的值來同步鎖的狀態。 通過這個方式,實現了可重入。
- ReentrantLock具備公平鎖和非公平鎖,默認使用非公平鎖。其實現原理主要依賴于AQS中的同步隊列。
最后,可中斷的機制是內部通過Thread.interrupted()判斷當前線程是否已被中斷,如果被中斷就拋出InterruptedException異常來實現的。