Java8 ReentrantLock 源码分析
一、ReentrantLock 概述
1.1 ReentrantLock 簡介
故名思義,ReentrantLock 意為可重入鎖,那么什么是可重入鎖呢?可重入意為一個持有鎖的線程可以對資源重復加鎖而不會阻塞。比如下面這樣:
public synchronized void f1() {f2();}private synchronized void f2() { }ReentrantLock 除了支持可重入以外,還支持定義公平與非公平策略,默認情況下采用非公平策略。公平是指等待時間最長的線程會優先獲取鎖,也就是獲取鎖是順序的,可以理解為先到先得。
公平鎖保證了鎖的獲取按照 FIFO(先進先出)原則,但是需要大量的線程切換。非公平鎖雖然減少了線程之間的切換增大了其吞吐量,但是可能會造成線程“饑餓”。
1.2 使用方式
public void f1() {ReentrantLock lock = new ReentrantLock();try {lock.lock();// ...} finally {lock.unlock();}}與 synchronized 不同,使用 ReentrantLock 必須顯示的加鎖與釋放鎖。
1.3 與 synchronized 對比
相同點:
- 可重入,同一線程可以多次獲得同一個鎖
- 都保證了可見性和互斥性
不同點:
- ReentrantLock 可響應中斷、可輪回,為處理鎖的不可用性提供了更高的靈活性,synchronized 不可以響應中斷
- ReentrantLock 是 API 級別的,synchronized 是 JVM 級別(JVM 內置屬性)的,因此內置鎖可以與特定的棧幀關聯起來
- ReentrantLock 可以實現公平鎖,切可以實現帶有時間限制的操作
- ReentrantLock 通過 Condition 可以綁定多個條件
二、源碼分析
了解了 ReentrantLock 的基本概念后,接下來就一起來看源碼吧。其實 ReentrantLock 內的源碼并不多,原因在于很多源碼都在 AbstractQueuedSynchronizer 中,因此想要了解 ReentrantLock 源碼的小伙伴需要先行了解下 AQS。
后面會給大家推薦幾篇關于 AQS 源碼介紹的文章,有興趣的可以自行了解,或者到我的 GitHub 去查下相應的源碼記錄。點我前往 GitHub
2.1 構造函數
默認構造函數
public ReentrantLock() {sync = new NonfairSync();}有參構造函數
public ReentrantLock(boolean fair) {sync = fair ? new FairSync() : new NonfairSync();}上面我們說了 ReentrantLock 支持兩種同步策略,分別是公平與非公平,從這個構造函數中就能體現出來,相信大家看了這個構造函數也應該想到這兩個 FairSync,NonfairSync 對象應該發揮著至關重要的作用。下面就來分別分析下對應的源碼。
2.2 NonfairSync
static final class NonfairSync extends Sync {private static final long serialVersionUID = 7316153563782823691L;// 加鎖final void lock() {// 無鎖重入,通過 CAS 第一次嘗試修改同步狀態值if (compareAndSetState(0, 1))// 設置當前線程獨占setExclusiveOwnerThread(Thread.currentThread());else// 鎖重入,同步狀態值 + 1acquire(1);}// 嘗試獲取鎖,直接調用 Sync 中的 nonfairTryAcquire 方法即可protected final boolean tryAcquire(int acquires) {return nonfairTryAcquire(acquires);}}acquire 方法是 AQS 中的一個模板方法,這里我們就不多介紹了,還是要提醒下,大家應該先了解 AQS,再來看這篇文章。
tryAcquire 調用的是 nonfairTryAcquire 方法嘗試去獲取同步狀態,接下來我們看 Sync 類中具體做了哪些實現。
2.3 Sync
abstract static class Sync extends AbstractQueuedSynchronizer {abstract void lock();// 獨占模式下嘗試獲取鎖final boolean nonfairTryAcquire(int acquires) {final Thread current = Thread.currentThread();// 獲取同步狀態int c = getState();// 0 表示無狀態,獨占鎖模式下可理解為沒有鎖重入if (c == 0) {// 更新同步狀態值if (compareAndSetState(0, acquires)) {// 獨占式設置當前線程setExclusiveOwnerThread(current);return true;}}// 設置獨占鎖可重入else if (current == getExclusiveOwnerThread()) {int nextc = c + acquires;if (nextc < 0) // overflowthrow new Error("Maximum lock count exceeded");// 重新設置修改后同步的狀態值setState(nextc);return true;}// 獲取失敗return false;}// 獨占模式下嘗試釋放鎖protected final boolean tryRelease(int releases) {// 減小同步狀態int c = getState() - releases;// 不是當前線程拋出異常if (Thread.currentThread() != getExclusiveOwnerThread())throw new IllegalMonitorStateException();boolean free = false;// 如果同步狀態只為 0,表示可以釋放資源,后面的線程可以嘗試去獲取鎖if (c == 0) {free = true;setExclusiveOwnerThread(null);}// 更新減小后的同步狀態值setState(c);return free;}}上面并沒有把 Sync 中的所有源碼貼出來,這些源碼已經足夠我們說明問題了。
首先來看 nonfairTryAcquire 方法,該方法用于非公平模式下獲取同步狀態,我們知道 ReentrantLock 是支持可重入的鎖,因此要考慮兩種情況,沒有鎖重入與有鎖重入。其實源碼也很簡單,有鎖重入的情況下只要增加同步狀態值即可,這里的同步狀態值可以理解為鎖重入的次數。等釋放鎖時將同步狀態值再逐次減小,當減小為 0 時表示鎖已經釋放,這也是 tryRelease 方法中做的事情。
ReentrantLock 獲取鎖的方式是獨占的,因此釋放鎖的過程,公平與非公平模式下是相同的。因為考慮到重入的情況,只有當同步狀態減小到為 0 時,才返回 true 表示釋放鎖成功。
2.4 FairSync
static final class FairSync extends Sync {final void lock() {acquire(1);}// 嘗試獲取鎖protected final boolean tryAcquire(int acquires) {// 獲取當前線程final Thread current = Thread.currentThread();// 獲取同步狀態值int c = getState();// 在同步狀態為 0 的情況下并不是所有線程都可以去獲取同步狀態,等待時間長的線程會優先獲取同步狀態if (c == 0) {// 如果隊列中沒有等待時間比當前線程時間長的線程,更新同步狀態值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);return true;}return false;}}公平策略下獲取同步狀態的過程與非同步狀態是類似的,只不過是多了一個 hasQueuedPredecessors 過程判斷,這個方法用于判斷當前線程對應的節點是否還有前驅節點,如果有則獲取失敗。下面是具體代碼實現:
public final boolean hasQueuedPredecessors() {Node t = tail; // Read fields in reverse initialization orderNode h = head;Node s;// s 表示頭節點的后繼節點,如果頭節點的后繼節點不是當前線程的節點,// 表示當前線程并非較先加入隊列的線程,因此不能成功獲取同步狀態return h != t &&((s = h.next) == null || s.thread != Thread.currentThread());}非公平策略下釋放同步狀態與公平策略下相同,上面已經概述過了。
jdk1.8 源碼閱讀:https://github.com/zchen96/jdk1.8-source-code-read
參考資料
《JAVA 并發編程的藝術》
深入理解AbstractQueuedSynchronizer(一)
Java并發之AQS詳解
總結
以上是生活随笔為你收集整理的Java8 ReentrantLock 源码分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 坦克500后面小书包是斜的
- 下一篇: 水车柴火腊肉属于哪儿的特产