?? 嗨,您好 ?? 我是 vnjohn,在互聯(lián)網(wǎng)企業(yè)擔(dān)任 Java 開發(fā),CSDN 優(yōu)質(zhì)創(chuàng)作者
?? 推薦專欄:Spring、MySQL、Nacos、Java,后續(xù)其他專欄會(huì)持續(xù)優(yōu)化更新迭代
??文章所在專欄:JUC
?? 我當(dāng)前正在學(xué)習(xí)微服務(wù)領(lǐng)域、云原生領(lǐng)域、消息中間件等架構(gòu)、原理知識(shí)
?? 向我詢問任何您想要的東西,ID:vnjohn
??覺得博主文章寫的還 OK,能夠幫助到您的,感謝三連支持博客??
?? 代詞: vnjohn
? 有趣的事實(shí):音樂、跑步、電影、游戲
前言
介紹 ReentrantLock、AQS 之前,先分析它們的來源,來自于 JUC 中的核心組件,java.util.concurrent 在并發(fā)編程中是比較會(huì)常用的工具類,里面包含了很多在并發(fā)場(chǎng)景下使用的組件,比如:線程池 > ThreadPoolExecutor、阻塞隊(duì)列 > BlockingQueue、計(jì)數(shù)器 > CountDownLatch、循環(huán)屏障 > CyclicBarrier、信號(hào)量 > Semaphore、并發(fā)集合 > ConcurrentHashMap | CopyOnWriteArrayList |ConcurrentSkipListMap 等
ReentrantLock 與 synchronized 具有相同的基本行為、語(yǔ)義,但它擴(kuò)展了一些其他的功能且更能靈活控制鎖
1、ReentrantLock 提供了公平鎖、非公平鎖的機(jī)制,而 synchronized 并沒有公平鎖的機(jī)制
2、ReentrantLock 提供了 tryLock 方法,嘗試獲取鎖而不會(huì)阻塞線程去作其他的事情,更加靈活
3、ReentrantLock#lockInterruptibly 方法提供了響應(yīng)中斷的能力,若當(dāng)前在等待鎖的線程被中斷了,通過此方法可以捕獲到中斷異常,以便作相應(yīng)的異常處理
4、ReentrantLock > tryLock(long time, TimeUnit unit) 方法提供了鎖超時(shí)等待能力,可以指定等待鎖的超時(shí)時(shí)間,對(duì)于限時(shí)等待的場(chǎng)景很有用
5、ReentrantLock 可以通過 newCondition 方法獲取多個(gè) Condition 對(duì)象來實(shí)現(xiàn)多個(gè)條件變量,以便可以更加細(xì)粒度地調(diào)用 await、signal 等待、喚醒操作;synchronized 只能通過 wait、notify 方法實(shí)現(xiàn)簡(jiǎn)單的等待和喚醒
在該篇博文主要介紹 ReentrantLock 是如何實(shí)現(xiàn)的,以及它的核心方法源碼,如何結(jié)合 AQS 實(shí)現(xiàn)鎖解決并發(fā)安全問題的
Lock
Lock 是 JUC 組件下最核心的接口,絕大部分組件都使用到了 Lock 接口,所以先以 Lock 接口作為切入點(diǎn)講解后續(xù)的源碼
Lock 本質(zhì)上是一個(gè)接口,它提供了獲得鎖、釋放鎖、條件變量、鎖中斷能力,定義為接口就意味著它定義了一個(gè)鎖的標(biāo)準(zhǔn)規(guī)范,也同時(shí)意味著鎖的不同實(shí)現(xiàn)。實(shí)現(xiàn) Lock 接口的類有很多,以下為幾個(gè)常見的鎖實(shí)現(xiàn)
- ReentrantLock:表示為重入鎖,它是唯一一個(gè)實(shí)現(xiàn)了 Lock 接口的類;重入鎖是指當(dāng)前線程獲得鎖以后,再次獲取鎖不需要進(jìn)行阻塞,而是直接累加 AbstractQueuedSynchronizer#state 變量值
- ReentrantReadWriteLock:表示重入讀寫鎖,實(shí)現(xiàn)了 ReadWriteLock 接口,在該類中維護(hù)了兩種鎖,一個(gè)是 ReadLock,另外一個(gè)是 WriteLock,它們各自實(shí)現(xiàn)了 Lock 接口。
讀寫鎖是一種適合讀多寫少的場(chǎng)景下,來解決線程安全問題的組件,基本的原則:讀讀不互斥、讀寫互斥、寫寫互斥,一旦涉及到數(shù)據(jù)變化的操作都會(huì)是互斥的
- StampedLock:該鎖是 JDK 8 引入的鎖機(jī)制,是讀寫鎖的一個(gè)改進(jìn)版本,讀寫鎖雖然通過分離讀、寫功能使得讀、讀之間可以并行,但是讀、寫是互斥的,若大量的讀線程存在,可能會(huì)引起寫線程的饑餓;StampedLock 是一種樂觀鎖的讀策略,采用 CAS 樂觀鎖完全不會(huì)阻塞寫線程
重要的方法,簡(jiǎn)介如下:
- lock:若鎖可用就獲得鎖,若鎖不可用就阻塞,直接鎖被釋放
- lockInterruptibly:與 lock 方法相似,但阻塞的線程可中斷,會(huì)拋出
java.lang.InterruptedException 異常
- tryLock:非阻塞獲取鎖,嘗試獲取鎖,若成功返回 true
- tryLock(long timeout, TimeUnit timeUnit):帶有超時(shí)時(shí)間的獲取鎖方法
- unLock:釋放鎖
重入鎖
重入鎖,支持同一個(gè)線程在同一個(gè)時(shí)刻獲取同一把鎖;也就是說,若當(dāng)前線程 T1 調(diào)用 lock 方法獲取了鎖以后,再次調(diào)用 lock,是不會(huì)再以阻塞的方式去獲取鎖的,直接增加鎖的重入次數(shù)就 OK 了。
synchronized、ReentrantLock 都支持重入鎖,存在多個(gè)加鎖的方法相互調(diào)用時(shí),其實(shí)就是一種鎖可重入特性的場(chǎng)景,以下通過不同的代碼案例來演示可重入鎖是怎樣的
synchronized
/**
* @author vnjohn
* @since 2023/6/17
*/
public class SynchronizedDemo {
public synchronized void lockMethodOne() {
System.out.println("begin:lockMethodOne");
lockMethodTwo();
}
public void lockMethodTwo() {
synchronized (this) {
System.out.println("begin:lockMethodTwo");
}
}
public static void main(String[] args) {
SynchronizedDemo synchronizedDemo = new SynchronizedDemo();
new Thread(() -> synchronizedDemo.lockMethodOne()).start();
}
}
調(diào)用 lockMethodOne 方法獲取了當(dāng)前實(shí)例的鎖,然后在這個(gè)方法里面還調(diào)用了 lockMethodTwo 方法,lockMethodTwo 雖然是代碼塊鎖,但鎖住的也是當(dāng)前實(shí)例;若不支持鎖可重入時(shí),當(dāng)前線程會(huì)因?yàn)闊o法獲取 lockMethodTwo 實(shí)例鎖而被阻塞,即會(huì)發(fā)生死鎖現(xiàn)象,重入鎖設(shè)計(jì)的目的是為了避免線程的死鎖
ReentrantLock
ReentrantLock 與 synchronized 同理,示例代碼如下:
/**
* @author vnjohn
* @since 2023/6/17
*/
public class ReentrantLockDemo {
static Lock lock = new ReentrantLock();
public void lockMethodOne() {
lock.lock();
try {
System.out.println("begin:lockMethodOne");
lockMethodTwo();
} finally {
lock.unlock();
}
}
public void lockMethodTwo() {
lock.lock();
try {
System.out.println("begin:lockMethodTwo");
} finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
ReentrantLockDemo reentrantLockDemo = new ReentrantLockDemo();
new Thread(()-> reentrantLockDemo.lockMethodOne()).start();
}
}
ReentrantReadWriteLock 讀寫鎖
上面提及到的 synchronized、ReentrantLock 重入鎖的特性其實(shí)是排它鎖,也是悲觀鎖,該鎖在同一時(shí)刻只允許一個(gè)線程進(jìn)行訪問,而讀寫鎖在同一個(gè)時(shí)刻可以允許多個(gè)線程(讀)訪問,但是在寫線程訪問時(shí),所有的讀線程、其他寫線程都會(huì)被阻塞。讀寫鎖維護(hù)了一對(duì)鎖:讀鎖 > ReentrantReadWriteLock.ReadLock、寫鎖 > ReentrantReadWriteLock.WriteLock;一般情況下,讀寫鎖的性能會(huì)比悲觀鎖性能好,因?yàn)樵诖蠖鄶?shù)場(chǎng)景下讀都是多于寫的,讀寫鎖能夠比排它鎖提供更好的并發(fā)性、吞吐量;通過案例來演示讀寫鎖如何使用,如下:
/**
* @author vnjohn
* @since 2023/6/18
*/
public class ReentrantReadWriteLockDemo {
private static Map<String, Object> CACHE_MAP = new HashMap<>();
private static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private static Lock readLock = readWriteLock.readLock();
private static Lock writeLock = readWriteLock.writeLock();
/**
* 通過讀鎖從本地緩存中獲取數(shù)據(jù)
*
* @param key
* @return
*/
public static Object get(String key) {
readLock.lock();
try {
System.out.println("本地緩存讀取數(shù)據(jù):" + key);
TimeUnit.SECONDS.sleep(1);
return CACHE_MAP.get(key);
} catch (InterruptedException e) {
return null;
} finally {
readLock.unlock();
}
}
/**
* 通過寫鎖從本地緩存中獲取數(shù)據(jù)
*
* @param key
* @return
*/
public static Object put(String key, Object obj) {
writeLock.lock();
try {
System.out.println("本地緩存寫入數(shù)據(jù):" + key);
TimeUnit.SECONDS.sleep(1);
return CACHE_MAP.put(key, obj);
} catch (InterruptedException e) {
return null;
} finally {
writeLock.unlock();
}
}
public static void main(String[] args) {
String keyOnce = "thread-batch-once";
for (int i = 0; i < 5; i++) {
// 演示讀寫鎖互斥的情況,
new Thread(()-> ReentrantReadWriteLockDemo.get(keyOnce)).start();
new Thread(()-> ReentrantReadWriteLockDemo.put(keyOnce, Thread.currentThread().getName())).start();
}
}
}
在該案例中,通過 HashMap 來模擬了一個(gè)本地緩存,然后使用讀寫鎖來保證這個(gè)本地緩存線程安全性。當(dāng)執(zhí)行讀操作的時(shí)候,需要獲取讀鎖,在并發(fā)訪問的時(shí)候,讀鎖不會(huì)阻塞,因?yàn)樽x操作不會(huì)影響執(zhí)行結(jié)果
在執(zhí)行寫操作時(shí),線程必須要獲取寫鎖,當(dāng)已經(jīng)有線程持有寫鎖的情況下,當(dāng)前線程會(huì)被阻塞,只有當(dāng)鎖釋放以后,其他讀寫操作才能繼續(xù)執(zhí)行。使用讀寫鎖提升讀操作的并發(fā)性,也保證每次寫操作對(duì)所有的讀寫操作的可見性
讀鎖、讀鎖可以共享
讀鎖、寫鎖不可以共享(排它)
寫鎖、寫鎖不可以共享(排它)
ReentrantLock 實(shí)現(xiàn)原理
鎖的基本原理是,將多線程并行任務(wù)基于某一種機(jī)制實(shí)現(xiàn)線程的串行執(zhí)行,從而達(dá)到線程安全性的目的。在 synchronize 中,存在鎖升級(jí)的概念 > 偏向鎖、輕量級(jí)鎖、重量級(jí)鎖。基于 CAS 樂觀自旋鎖優(yōu)化了 synchronize 加鎖開銷,同時(shí)在重量級(jí)鎖階段,通過線程的阻塞以及喚醒來達(dá)到線程競(jìng)爭(zhēng)、同步的目的。那么在 ReentrantLock 中,也一定會(huì)存在這樣的問題需要去解決
那么在多線程競(jìng)爭(zhēng)重入鎖時(shí),競(jìng)爭(zhēng)失敗的線程是如何實(shí)現(xiàn)阻塞以及被喚醒的呢?提及這個(gè)必須先說說 AQS 是什么了!
AQS
AQS > 全稱 AbstractQueuedSynchronizer,內(nèi)部用到了一個(gè)同步等待隊(duì)列,它是一個(gè)同步工具也是 Lock 用來實(shí)現(xiàn)線程同步的核心組件
從 AQS 功能、使用層面來說,AQS 分為兩種:獨(dú)占、共享
- 獨(dú)占鎖:同一時(shí)刻只能有一個(gè)線程持有鎖,操作、寫入資源,比如:ReentrantLock
- 共享鎖:允許多個(gè)線程同時(shí)獲取鎖,并發(fā)訪問共享資源,比如:ReentrantReadWriteLock
AQS 內(nèi)部實(shí)現(xiàn)
AQS 內(nèi)部的同步等待隊(duì)列其實(shí)就是維護(hù)了一個(gè) FIFO 的雙向鏈表,這種結(jié)構(gòu)的特點(diǎn)是每個(gè)節(jié)點(diǎn)都會(huì)兩個(gè)指針,分別指向直接后繼節(jié)點(diǎn)、直接前驅(qū)節(jié)點(diǎn)。所以雙向鏈表可以從任意一個(gè)節(jié)點(diǎn)開始很方便的訪問前驅(qū)、后繼節(jié)點(diǎn)。節(jié)點(diǎn)由內(nèi)部類 Node 表示,Node 內(nèi)部類其實(shí)是由線程封裝,當(dāng)線程爭(zhēng)搶鎖失敗后會(huì)封裝成 Node 加入到 AQS 隊(duì)列中去;當(dāng)獲取鎖的線程釋放鎖以后,會(huì)從隊(duì)列中喚醒其中一個(gè)阻塞的節(jié)點(diǎn)(線程)
Node 內(nèi)部結(jié)構(gòu)
static final class Node {
/** Marker to indicate a node is waiting in shared mode */
static final Node SHARED = new Node();
/** Marker to indicate a node is waiting in exclusive mode */
static final Node EXCLUSIVE = null;
/** 線程已取消等待鎖,調(diào)用 tryLock(TimeUnit) 或 intercept 中斷方法*/
static final int CANCELLED = 1;
/** 表明后續(xù)線程需要被喚醒 */
static final int SIGNAL = -1;
/** 表明線程在等待狀態(tài) */
static final int CONDITION = -2;
/**
* 在共享模式下,該值表明下一個(gè)需要被分享的節(jié)點(diǎn)應(yīng)該無條件被分享
*/
static final int PROPAGATE = -3;
/**
* 0-默認(rèn)值、CANCELLED、SIGNAL、CONDITION、PROPAGATE,
* 后續(xù)會(huì)通過 CAS 操作改變?cè)撝禒顟B(tài)
*/
volatile int waitStatus;
/**
* 前驅(qū)節(jié)點(diǎn)
*/
volatile Node prev;
/**
* 后繼節(jié)點(diǎn)
*/
volatile Node next;
/**
* 當(dāng)前線程
*/
volatile Thread thread;
/**
* 存儲(chǔ)在 Condition 隊(duì)列中的后繼節(jié)點(diǎn)
*/
Node nextWaiter;
/**
* 是否為共享模式(共享鎖)
*/
final boolean isShared() {
return nextWaiter == SHARED;
}
/**
* 獲取前驅(qū)節(jié)點(diǎn)或拋出空指針異常
* @return the predecessor of this node
*/
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
// 用于建立初始的頭或共享標(biāo)記
Node() { // Used to establish initial head or SHARED marker
}
// 該構(gòu)造方法會(huì)構(gòu)造成一個(gè) Node,添加到等待隊(duì)列中
Node(Thread thread, Node mode) { // Used by addWaiter
this.nextWaiter = mode;
this.thread = thread;
}
// 該構(gòu)造方法會(huì)在 Condition 隊(duì)列中使用
Node(Thread thread, int waitStatus) { // Used by Condition
this.waitStatus = waitStatus;
this.thread = thread;
}
}
Node 變更過程
當(dāng)出現(xiàn)鎖競(jìng)爭(zhēng)或釋放鎖時(shí), AQS 同步等待隊(duì)列中的節(jié)點(diǎn)會(huì)發(fā)生變化
添加節(jié)點(diǎn)
下面來看一下添加節(jié)點(diǎn)的場(chǎng)景是怎樣的
在這里會(huì)發(fā)生三個(gè)變化:
- 新的競(jìng)爭(zhēng)鎖線程會(huì)封裝成 Node 節(jié)點(diǎn)追加到同步隊(duì)列中,設(shè)置 prev 節(jié)點(diǎn)指向原有的 tail 尾部節(jié)點(diǎn)
- 通過 CAS 操作將 tail 指針指向新加入的 Node 節(jié)點(diǎn)
- 修改原有的 tail 尾部節(jié)點(diǎn) next 指針指向新加入的 Node
以上的變化發(fā)生在核心方法:AbstractQueuedSynchronizer#addWaiter 中
釋放節(jié)點(diǎn)
head 節(jié)點(diǎn)表示獲取鎖成功的節(jié)點(diǎn),當(dāng)頭節(jié)點(diǎn)在釋放同步狀態(tài)時(shí),會(huì)喚醒后繼節(jié)點(diǎn),若后繼節(jié)點(diǎn)獲取鎖成功,會(huì)將自身設(shè)置為頭節(jié)點(diǎn),節(jié)點(diǎn)的變化過程如下:
在這里會(huì)發(fā)生兩個(gè)變化:
- 設(shè)置 head 頭節(jié)點(diǎn)指向下一個(gè)獲取鎖的節(jié)點(diǎn)
- 新的獲取鎖節(jié)點(diǎn),將 prev 指針指向 null
設(shè)置 head 頭節(jié)點(diǎn)不需要使用 CAS 操作,原因:設(shè)置 head 頭節(jié)點(diǎn)是由獲取鎖的線程來完成的,同步鎖只能由一個(gè)線程獲取,所以不適合通過 CAS 來保證,只需要把 head 頭節(jié)點(diǎn)設(shè)置為原 head 頭節(jié)點(diǎn)的后繼節(jié)點(diǎn),并且切斷原 head 頭節(jié)點(diǎn)的 next 引用即可
ReentrantLock 類源碼分析
以 ReentrantLock 類作為入口,看看該類源碼級(jí)別是如何使用 AQS 來實(shí)現(xiàn)線程同步的
時(shí)序圖
ReentrantLock#lock 方法源碼的調(diào)用過程,通過時(shí)序圖的方式來進(jìn)行展示
鎖競(jìng)爭(zhēng)核心方法
簡(jiǎn)單梳理了一下 lock 流程以后,下面來介紹 ReentrantLock、AQS 中一些核心方法內(nèi)容以及其作用
NonfairSync#lock
NonfairSync 實(shí)現(xiàn)類是 ReentrantLock 類內(nèi)部接口 Sync 的實(shí)現(xiàn)類,它采用非公平鎖的方式進(jìn)行鎖競(jìng)爭(zhēng), 下面來看其源碼內(nèi)容是如何實(shí)現(xiàn)的
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
非公平鎖、公平鎖最大的區(qū)別:在于非公平鎖搶占鎖的邏輯是不管有沒有等待隊(duì)列中有沒有線程在排隊(duì),我先上來用 CAS 操作搶占一下
- CAS 成功,即表示成功獲取到了鎖
- CAS 失敗,調(diào)用 AbstractQueuedSynchronizer#acquire 方法走競(jìng)爭(zhēng)鎖邏輯
CAS(Compare And Set-比較并交換)
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
通過 CAS 樂觀鎖的方式來作比較并替換,若當(dāng)前內(nèi)存中的 state 值與預(yù)期值 expect 相等,則替換為 update 值;更新成功返回 true,否則返回 false;該操作是原子性的,不會(huì)出現(xiàn)線程安全問題,這里面會(huì)涉及到 Unsafe 類的操作
state 是 AQS 中的一個(gè)屬性,它在不同的組件實(shí)現(xiàn)中所表達(dá)的含義不一樣,對(duì)于重入鎖 ReentrantLock 來說,它有以下兩個(gè)含義:
- 當(dāng) state = 0 時(shí),表示無鎖狀態(tài)
- 當(dāng) state = 1 時(shí),表示已經(jīng)有線程獲取到了鎖
因?yàn)?ReentrantLock 允許可重入,所以同一個(gè)線程多次獲取鎖時(shí),state 會(huì)遞增,比如:在一段代碼中,當(dāng)前線程重復(fù)獲取同一把鎖三次(未釋放的情況下)state 為 3;而在釋放鎖時(shí),同樣需要釋放 3 次直至 state = 0 其他線程才有資格去獲取這把鎖
AQS#acquire
acquire 方法是 AQS 中的核心方法,若 CAS 操作未能成功,說明 state 值已經(jīng)不為 0,此時(shí)繼續(xù)調(diào)用 acquire(1) 方法操作,如下:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
該方法分以下幾塊邏輯進(jìn)行:
- 通過 tryAcquire 嘗試去獲取獨(dú)占鎖,若成功返回 true,失敗返回 false
- 若 tryAcquire 執(zhí)行結(jié)果為 false,則會(huì)調(diào)用 addWaiter 方法,將當(dāng)前線程封裝成 Node 添加到 AQS 隊(duì)列的尾部
- acquireQueued:將 Node 作為參數(shù),通過自旋的方式去嘗試獲取鎖,這里會(huì)執(zhí)行線程的阻塞等待邏輯
ReentrantLock.NonfairSync#tryAcquire
NonfairSync#tryAcquire 方法重寫至 AQS 類,AQS 該方法并沒有實(shí)現(xiàn),而是拋出異常,具體的實(shí)現(xiàn)內(nèi)容交由給子類去進(jìn)行實(shí)現(xiàn),這里采用了設(shè)計(jì)模式 > 模版方法
具體的子類實(shí)現(xiàn):ReentrantLock.NonfairSync#tryAcquire,該方法作用:嘗試獲取一把鎖,若成功返回 true、失敗返回 false
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
// Sync#nonfairTryAcquire
final boolean nonfairTryAcquire(int acquires) {
// 獲取當(dāng)前線程
final Thread current = Thread.currentThread();
int c = getState();// 獲取 state 狀態(tài)
if (c == 0) { // 代表無鎖狀態(tài)
// CAS 替換 state 值,CAS 成功表示鎖獲取成功
if (compareAndSetState(0, acquires)) {
// 保存當(dāng)前獲取鎖的線程
setExclusiveOwnerThread(current);
return true;
}
}
// 若同一個(gè)線程多次獲取同一把鎖,直接增加鎖重入次數(shù)即可
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;
}
- 獲取當(dāng)前內(nèi)存中 state 鎖狀態(tài)值
- state 狀態(tài)為 0 代表當(dāng)前鎖處于無鎖狀態(tài),首次獲取鎖的線程可以通過 CAS 操作更新 state 鎖狀態(tài)值
- 若當(dāng)前線程等于鎖占有的線程,則增加鎖重入次數(shù)即可
- 其他情況,代表獲取鎖失敗的線程,執(zhí)行 AQS#acquire 方法中的 addWaiter(Node.EXCLUSIVE), arg) 方法 > 添加獨(dú)占模式的 Node 隊(duì)列節(jié)點(diǎn)
AQS#addWaiter
當(dāng) tryAcquire 方法獲取鎖失敗以后,則會(huì)先調(diào)用 addWaiter 將當(dāng)前線程封裝成 Node,源碼如下:
private Node addWaiter(Node mode) {
// 將當(dāng)前線程封裝為 Node 節(jié)點(diǎn)
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
// tail 指向 AQS 同步隊(duì)列中的尾部節(jié)點(diǎn),默認(rèn):null
Node pred = tail;
// tail 不為空的情況下,說明同步隊(duì)列中存在節(jié)點(diǎn)
if (pred != null) {
// 將當(dāng)前線程 Node prev 前驅(qū)節(jié)點(diǎn)指針指向原來的尾部節(jié)點(diǎn)
node.prev = pred;
// 通過 CAS 操作將當(dāng)前 Node 設(shè)置為尾部節(jié)點(diǎn)
if (compareAndSetTail(pred, node)) {
// 設(shè)置成功以后,將原來的尾部節(jié)點(diǎn) next 后繼節(jié)點(diǎn)指向當(dāng)前 Node
pred.next = node;
return node;
}
}
// tail 為空的情況下,調(diào)用 enq 方法將當(dāng)前 Node 添加到同步隊(duì)列中
enq(node);
return node;
}
入?yún)ⅲ簃ode 表示節(jié)點(diǎn)的狀態(tài),ReentrantLock 傳入的狀態(tài)參數(shù):Node.EXCLUSIVE 代表獨(dú)占模式,意味著重入鎖獲取鎖采用獨(dú)占的方式,addWaiter 方法基本的執(zhí)行過程,如下所示:
- 將當(dāng)前線程封裝為 Node 節(jié)點(diǎn)對(duì)象
- 判斷當(dāng)前同步隊(duì)列中的尾部節(jié)點(diǎn)是否為空
- 若尾部節(jié)點(diǎn)不為空,通過 CAS 操作將當(dāng)前線程的 Node 添加到同步隊(duì)列中,并將新加入的 Node 設(shè)置為尾節(jié)點(diǎn),采用尾插法的方式進(jìn)行隊(duì)列入隊(duì)的
- 若尾部節(jié)點(diǎn)為空或者 CAS 設(shè)置尾部節(jié)點(diǎn)失敗,調(diào)用 enq 方法將當(dāng)前 Node 添加到同步隊(duì)列中
AQS#enq
該方法通過自旋的方式以便可以成功將當(dāng)前節(jié)點(diǎn)加入到同步隊(duì)列中
/**
* +------+ prev +-----+ +-----+
* head | | <---- | | <---- | | tail
* +------+ +-----+ +-----+
*/
private Node enq(final Node node) {
for (;;) {
Node t = tail;
// 尾節(jié)點(diǎn)為空,說明當(dāng)前同步隊(duì)列中未存在元素
// 初始化一個(gè)空對(duì)象 Node,先通過 CAS 將其設(shè)置為頭節(jié)點(diǎn),若成功再將其設(shè)置為尾節(jié)點(diǎn)
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
// 當(dāng)前節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)指向原來尾部節(jié)點(diǎn)、將當(dāng)前節(jié)點(diǎn)設(shè)置為尾部節(jié)點(diǎn)、原來尾部節(jié)點(diǎn)后繼節(jié)點(diǎn)指向當(dāng)前節(jié)點(diǎn)
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
enq 方法執(zhí)行只是為了維護(hù)同步等待隊(duì)列的節(jié)點(diǎn)元素,當(dāng)多個(gè)線程開始競(jìng)爭(zhēng)鎖時(shí),必然會(huì)進(jìn)行排隊(duì),第一次入隊(duì)的線程不僅要承擔(dān)將自身加入到隊(duì)列中,同時(shí)還需要初始化一個(gè)空 Node 對(duì)象,將其設(shè)置為頭尾節(jié)點(diǎn)
圖解分析
假設(shè)有 3 個(gè)線程同時(shí)來爭(zhēng)搶鎖,那么截止到 AQS#addWaiter 或 AQS#enq 方法結(jié)束之后,AQS 中同步等待隊(duì)列結(jié)構(gòu)圖,如下所示:
AQS#acquireQueued
當(dāng)執(zhí)行完 AQS#addWaiter 方法以后,會(huì)將返回的 Node 參數(shù)傳遞給 acquireQueued 方法,去實(shí)現(xiàn)鎖競(jìng)爭(zhēng)、阻塞線程邏輯,方法源碼如下:
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
// 獲取當(dāng)前 Node 節(jié)點(diǎn)的前驅(qū) prev 節(jié)點(diǎn)
final Node p = node.predecessor();
// 若前驅(qū) prev 節(jié)點(diǎn)為頭節(jié)點(diǎn),當(dāng)前 Node 重新嘗試獲取鎖
if (p == head && tryAcquire(arg)) {
// 獲取鎖成功,說明鎖已經(jīng)被持有的線程所釋放,設(shè)置當(dāng)前 Node 為頭節(jié)點(diǎn)
setHead(node);
// 將原 head 頭節(jié)點(diǎn)從同步隊(duì)列中移除
p.next = null; // help GC
failed = false;
return interrupted;
}
// 獲取鎖失敗,會(huì)獲取前驅(qū)節(jié)點(diǎn)并更新 waitStatus 狀態(tài)值
// 隨機(jī)調(diào)用原生鎖 LockSupport#park 方法阻塞當(dāng)前競(jìng)爭(zhēng)鎖的線程
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
acquireQueued 方法基本的執(zhí)行過程同時(shí)以 AQS#enq 分析中的圖解分析為例,如下所示:
- 獲取當(dāng)前節(jié)點(diǎn) Node prev 前驅(qū)節(jié)點(diǎn)
比如:當(dāng)前是線程 B,那么它的前驅(qū)節(jié)點(diǎn)就是 Thread 為 null 的頭節(jié)點(diǎn)
- 若 prev 前驅(qū)節(jié)點(diǎn)為 head 頭節(jié)點(diǎn),那么它有資格再去爭(zhēng)搶一次鎖,調(diào)用 ReentrantLock.NonfairSync#tryAcquire 方法搶占鎖
也就是說線程 B 在這里也會(huì)有一次機(jī)會(huì)再去爭(zhēng)奪鎖
- 若搶占鎖成功,把當(dāng)前搶到鎖的 Node 節(jié)點(diǎn)設(shè)置為 head 頭節(jié)點(diǎn),并且移除原有的頭節(jié)點(diǎn)
- 若搶占鎖失敗,先通過 shouldParkAfterFailedAcquire 方法更新一次 waitStatus 值狀態(tài),然后再調(diào)用原生鎖支持 > LockSupport.park(this) 阻塞當(dāng)前線程等待后續(xù)被喚醒
仍然以線程 A 未釋放鎖,線程 B 處于首節(jié)點(diǎn)的情況作以說明:由于 acquireQueued 方法是死循環(huán),所有的 Node 新建時(shí) waitStatus 屬性值都為 0 (除了 Condition 條件變量)第一次遍歷時(shí)會(huì)搶一次鎖;這一次會(huì)調(diào)用 shouldParkAfterFailedAcquire 方法將 waitStatus -> 0 更改為 SIGNAL-待喚醒狀態(tài),該方法執(zhí)行完以后會(huì)返回 false,然后會(huì)繼續(xù)第二次循環(huán),第二次執(zhí)行 shouldParkAfterFailedAcquire 方法返回 true,接著會(huì)調(diào)用 parkAndCheckInterrupt 方法使用原生鎖方式:LockSupport.park(this) > 阻塞當(dāng)前線程并返回當(dāng)前線程是否中斷的標(biāo)識(shí)
- 最后,通過 cancelAcquire 方法取消當(dāng)前線程獲取鎖的節(jié)點(diǎn)
AQS#shouldParkAfterFailedAcquire
線程 A 未釋放鎖,線程 B、線程 C 來爭(zhēng)搶鎖肯定會(huì)失敗,失敗以后會(huì)調(diào)用 shouldParkAfterFailedAcquire 方法,Node#waitStatus 存在五種狀態(tài),如下:
- CANCELLED:值為 1,即為結(jié)束狀態(tài),在同步等待隊(duì)列中等待的線程超時(shí)或被中斷,需要從同步隊(duì)列中取消該線程 Node 節(jié)點(diǎn),進(jìn)入該狀態(tài)以后的節(jié)點(diǎn)將不再發(fā)生變化
- 0:初始化狀態(tài)
- SIGNAL:值為 -1,當(dāng)前驅(qū)節(jié)點(diǎn)釋放鎖以后,就會(huì)通知標(biāo)識(shí)為 SIGNAL 狀態(tài)的后繼 Node 節(jié)點(diǎn)線程
- CONDITION:值為 -2,與 ReentrantLock#newCondition 條件變量有關(guān)系,AQS.ConditionObject#addConditionWaiter 在該方法中會(huì)提現(xiàn)出來
- PROPAGATE:值為 -3,在共享模式下,PROPAGATE 處于可運(yùn)行狀態(tài)
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
// 若前驅(qū)節(jié)點(diǎn)為 SIGNAL,意味著只需要等待其他前驅(qū)節(jié)點(diǎn)的線程被釋放
// 當(dāng)獲取鎖的線程調(diào)用 release 方法后,該前驅(qū)節(jié)點(diǎn)的線程就會(huì)被喚醒
if (ws == Node.SIGNAL)
// 返回 true,意味著當(dāng)前線程可以放心調(diào)用 parkAndCheckInterrupt 方法進(jìn)行掛起
return true;
// waitState 大于 0,意味著 prev 前驅(qū)節(jié)點(diǎn)取消了排隊(duì)操作,直接將這個(gè)節(jié)點(diǎn)移除即可
if (ws > 0) {
// 相當(dāng)于:pred=pred.prev;node.prev=pred;
// 從尾部節(jié)點(diǎn)開始查找,直到將所有的 CANCELLED 節(jié)點(diǎn)移除
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
// 使用 CAS 設(shè)置前驅(qū) prev 節(jié)點(diǎn)狀態(tài)為 SIGNAL
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
該方法主要作用:通過 Node 節(jié)點(diǎn)狀態(tài)來判斷,線程 B、線程 C 競(jìng)爭(zhēng)鎖失敗后是否應(yīng)該要被掛起
- 若 Thread-B、Thread-C 前驅(qū) prev 節(jié)點(diǎn)狀態(tài)為 SIGNAL,表示可以放心掛起所在的當(dāng)前線程
- 若當(dāng)前線程 prev 節(jié)點(diǎn)狀態(tài)為 CANCELLED,采用循環(huán)方式掃描同步等待隊(duì)列將 CANCELLED 狀態(tài)的節(jié)點(diǎn)從同步等待隊(duì)列中移除
- 以上兩個(gè)條件都滿足,將前驅(qū) prev 節(jié)點(diǎn)狀態(tài)改為 SIGNAL,返回 false
該方法返回 true、false 代表的含義不同,當(dāng)返回 true 時(shí),不會(huì)進(jìn)入 AQS#acquireQueued 方法的下一次循環(huán),會(huì)調(diào)用 parkAndCheckInterrupt 方法將當(dāng)前線程阻塞;當(dāng)返回 false 時(shí),會(huì)進(jìn)入到 AQS#acquireQueued 方法的下一次循環(huán)再次嘗試爭(zhēng)搶一次鎖,當(dāng)搶鎖成功當(dāng)前線程就是獨(dú)占線程,搶鎖失敗再調(diào)用 parkAndCheckInterrupt 方法將當(dāng)前線程阻塞
AQS#parkAndCheckInterrupt
parkAndCheckInterrupt 方法邏輯比較簡(jiǎn)單,先看源碼,如下:
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
- 調(diào)用 LockSupport#park 方法掛起當(dāng)前線程編程 waiting 狀態(tài)
LockSupport 原生鎖支持
park 方法:阻塞當(dāng)前線程,不需要使用 sync 修飾,直接可以使用
unpark 方法:?jiǎn)拘阎付ň€程
unpark 方法可以先于 park 方法先調(diào)用,unpark 相當(dāng)于是獲取許可數(shù)量 1、park 相當(dāng)于是消費(fèi)許可數(shù)量 1
- Thread#interrupted:返回當(dāng)前線程是否被其他線程觸發(fā)過中斷請(qǐng)求,也就是調(diào)用 Thread#interrupt 方法;若有觸發(fā)過中斷請(qǐng)求,那么該方法會(huì)返回當(dāng)前的中斷標(biāo)識(shí)為 true,并且會(huì)對(duì)中斷標(biāo)識(shí)進(jìn)行復(fù)位標(biāo)識(shí)已經(jīng)響應(yīng)過了中斷請(qǐng)求,也就是會(huì)在 AQS#acquire 方法中執(zhí)行 selfInterrupt 方法
- selfInterrupt:標(biāo)識(shí)當(dāng)前線程是否執(zhí)行 AQS#acquireQueued 方法時(shí)被中斷過,若被中斷過,則需要響應(yīng)中斷請(qǐng)求,因?yàn)樵诰€程調(diào)用 AQS#acquireQueued 方法是不會(huì)去響應(yīng)中斷請(qǐng)求的
通過 AQS#acquireQueued 方法來競(jìng)爭(zhēng)鎖,若 Thread-A 仍然還在執(zhí)行中未釋放鎖,那么 Thread-B、Thread-C 還會(huì)繼續(xù)掛起
到這里,鎖相關(guān)的競(jìng)爭(zhēng)方法在這里基本上都介紹過了,其實(shí)看到這里,能發(fā)現(xiàn),當(dāng)競(jìng)爭(zhēng)的鎖線程失敗時(shí),會(huì)調(diào)用 LockSupport#park 方法阻塞住,等待鎖匙放時(shí),還會(huì)有 LockSuppor#unpark 方法進(jìn)行鎖匙放,下面就來分析鎖匙放時(shí)的一些核心方法是如何處理的!?。?/p>
鎖釋放核心方法
若此時(shí) Thread-A 釋放鎖了,那么接下來 Thread-B、Thread-C 是如何走的呢?
ReentrantLock#unlock
public void unlock() {
sync.release(1);
}
在 unlock 方法中,會(huì)調(diào)用其內(nèi)部類 Sync#release 方法,但由于 Sync 并未其父類 AQS#release 方法,所以它會(huì)延用其父類 AQS#release 方法的處理邏輯,源碼如下:
public final boolean release(int arg) {
// 若釋放占用當(dāng)前鎖的節(jié)點(diǎn) Node 線程成功
if (tryRelease(arg)) {
// 獲取 AQS 同步等待隊(duì)列中的 head 頭節(jié)點(diǎn)
Node h = head;
// 若 head 節(jié)點(diǎn)不為空 & waitStatus 非默認(rèn)值,直接喚醒下一個(gè)節(jié)點(diǎn)去爭(zhēng)搶鎖
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
該方法主要的執(zhí)行流程分為幾步,如下:
- 先調(diào)用 ReentrantLock.Sync#tryRelease 方法探測(cè)鎖釋放是否可以成功,它來自 AQS 子類 ReentrantLock.Sync 所實(shí)現(xiàn)的
- 獲取同步等待隊(duì)列中的 head 首節(jié)點(diǎn),若其不為空,并且它的 waitStatus 屬性值非默認(rèn)值 0,那么就會(huì)調(diào)用 unparkSuccessor 方法喚醒隊(duì)列中的下一個(gè)節(jié)點(diǎn)
ReentrantLock.Sync#tryRelease
該方法也體現(xiàn)了鎖重入次數(shù)的操作,源代碼如下:
protected final boolean tryRelease(int releases) {
// 當(dāng)前鎖線程重入次數(shù)減去要釋放的次數(shù)
int c = getState() - releases;
// 當(dāng)前線程不等于鎖持有線程,則判斷中斷監(jiān)聽鎖異常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
// 若減去后的鎖次數(shù)為 0
if (c == 0) {
// 返回 true、設(shè)置鎖持有線程為 null,其他線程就可以競(jìng)爭(zhēng)鎖了
free = true;
setExclusiveOwnerThread(null);
}
// 遞減鎖重入次數(shù),返回 false,鎖仍然被當(dāng)前線程所持有
setState(c);
return free;
}
ReentrantLock.Sync#tryRelease 執(zhí)行流程主要分析如下:
- 通過將 AQS#state 屬性值減少傳入的參數(shù)值(參數(shù):1)若減去的結(jié)果狀態(tài)值為 0,就將排它鎖 Owner 持有線程設(shè)置為 null,同時(shí)返回 true,以便于其他的線程有機(jī)會(huì)執(zhí)行競(jìng)爭(zhēng)鎖操作
- 若減去的結(jié)果狀態(tài)值不為 0,返回 free 變量默認(rèn)值 false,當(dāng)前線程仍然繼續(xù)持有這把鎖,其他線程暫時(shí)不可以爭(zhēng)搶鎖
在排它鎖中,加鎖時(shí) state 狀態(tài)會(huì)增加 1,在解鎖時(shí)會(huì)減去 1,同一把鎖,在被重入時(shí),可能會(huì)被疊加為 2、3、4 等,只有當(dāng)調(diào)用 unlock 方法次數(shù)與調(diào)用 lock 方法次數(shù)相對(duì)應(yīng),才會(huì)把鎖 Owner 持有線程設(shè)置為空,也只有這種情況下該方法執(zhí)行結(jié)果才有返回 true
AQS#unparkSuccessor
當(dāng) ReentrantLock.Sync#tryRelease 方法執(zhí)行完以后,會(huì)取同步等待隊(duì)列中首節(jié)點(diǎn),喚醒隊(duì)列中下一個(gè)節(jié)點(diǎn)去爭(zhēng)搶這把鎖,該方法源碼如下:
private void unparkSuccessor(Node node) {
// 獲取傳入節(jié)點(diǎn)的 waitStatus 屬性值
int ws = node.waitStatus;
if (ws < 0)
// 小于 0 通過 CAS 將其修改為 0
compareAndSetWaitStatus(node, ws, 0);
// 獲取傳入節(jié)點(diǎn)的后繼節(jié)點(diǎn)
Node s = node.next;
// 若后繼節(jié)點(diǎn)為空或者 waitStatus 大于 0 說明它是 CANCELLED-結(jié)束狀態(tài)
if (s == null || s.waitStatus > 0) {
s = null;
// 從尾部節(jié)點(diǎn)開始掃描,找到距離當(dāng)前傳入節(jié)點(diǎn)最近的一個(gè) waitStatus 小于等于 0 的節(jié)點(diǎn)
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
// 將同步等待隊(duì)列中 > 最前面的一個(gè)非 CANCELLED 狀態(tài)的 Node 線程進(jìn)行喚醒
if (s != null)
LockSupport.unpark(s.thread);
}
分析一下此方法分別會(huì)作那些事情,如下:
- 獲取當(dāng)前傳入節(jié)點(diǎn) Node waitStatus 屬性值,若它小于 0 時(shí),先通過 CAS 操作將其修改為 0
當(dāng)前節(jié)點(diǎn)其實(shí)就是 head 頭節(jié)點(diǎn),喚醒的操作不會(huì)喚醒頭節(jié)點(diǎn),只會(huì)喚醒頭節(jié)點(diǎn)后面不為 CANCELLED 狀態(tài)的首節(jié)點(diǎn) Node 線程
- 獲取當(dāng)前節(jié)點(diǎn)的 next 后繼節(jié)點(diǎn),若后繼節(jié)點(diǎn)為空或 waitStatus 大于 0(CANCELLED)那么就會(huì)遍歷該同步等待隊(duì)列,從尾部往前查找的方式,匹配到與當(dāng)前節(jié)點(diǎn)最近的一個(gè)非 CANCELLED 節(jié)點(diǎn),將其設(shè)置為待喚醒的節(jié)點(diǎn)
- 若待喚醒的節(jié)點(diǎn)不為空,調(diào)用原生鎖 LockSupport#unpark 方法將其喚醒,以便它可以再次去爭(zhēng)搶鎖
當(dāng)節(jié)點(diǎn)被喚醒后,比如:Thread-A 釋放鎖成功以后,會(huì)調(diào)用 AQS#unparkSuccessor 方法喚醒它的下一個(gè)節(jié)點(diǎn) Thread-B 所持有的(非 CANCELLED)
隨機(jī) Thread-B 被喚醒,它會(huì)繼續(xù)執(zhí)行 AQS#acquireQueued 方法中的循環(huán),執(zhí)行:if (p == head && tryAcquire(arg)) 代碼塊,所以后續(xù)被喚醒的線程都會(huì)是這樣,通過該代碼來確保同步隊(duì)列中的節(jié)點(diǎn)能夠獲取鎖資源
那么為什么在釋放鎖的時(shí)候一定要從尾部開始掃描呢?
回顧一下 AQS#enq 方法執(zhí)行的邏輯,插入新節(jié)點(diǎn)時(shí),它是從隊(duì)列尾部進(jìn)行節(jié)點(diǎn)入隊(duì)的,
看下圖紅色所標(biāo)注的
,在 CAS 操作成功之后,t.next = node; 操作之前,可能會(huì)存在其他線程調(diào)用 unlock 方法從 head 開始向后遍歷,由于 t.next = node; 還未執(zhí)行也就意味著同步等待隊(duì)列關(guān)系還未建立完整,就會(huì)導(dǎo)致遍歷到原始的尾部節(jié)點(diǎn)時(shí)被中斷 > 隊(duì)列中的鏈表關(guān)系斷鏈了;所以說,從后往前遍歷就不會(huì)出現(xiàn)這個(gè)問題
掛起線程被喚醒后執(zhí)行過程
當(dāng)持有鎖的線程調(diào)用 ReentrantLock#unlock 方法,原本被掛起的 Thread-B、Thread-C 線程就有機(jī)會(huì)被喚醒再繼續(xù)執(zhí)行,被喚醒之后的線程會(huì)繼續(xù)執(zhí)行 AQS#acquireQueued 方法內(nèi)的循環(huán),該方法在上面已經(jīng)分析過了,接下來以 Thread-B 被喚醒后為例,看它整個(gè)的執(zhí)行過程以及變化,以流程圖的方式呈現(xiàn)
同步等待隊(duì)列變更結(jié)構(gòu)圖:
同步等待隊(duì)列執(zhí)行過程流程圖:
博主是以如下源碼,對(duì) ReentrantLock、AQS 核心方法源碼進(jìn)行查看的,分享如下:
public class MultiThreadReentrantLockDemo {
private static final ReentrantLock LOCK = new ReentrantLock();
public void threadAProcess() {
LOCK.lock();
try {
System.out.println("執(zhí)行:threadAProcess 方法");
// 處理業(yè)務(wù)邏輯中....
// 斷點(diǎn)過程中該時(shí)間可以延長(zhǎng)
TimeUnit.SECONDS.sleep(6);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
LOCK.unlock();
}
}
public void threadBProcess() {
LOCK.lock();
try {
System.out.println("執(zhí)行:threadBProcess 方法");
// 處理業(yè)務(wù)邏輯中....
TimeUnit.SECONDS.sleep(60 * 5);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
LOCK.unlock();
}
}
public void threadCProcess() {
LOCK.lock();
try {
System.out.println("執(zhí)行:threadCProcess 方法");
// 處理業(yè)務(wù)邏輯中....
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
LOCK.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
MultiThreadReentrantLockDemo multiThreadLock = new MultiThreadReentrantLockDemo();
new Thread(()-> multiThreadLock.threadAProcess(), "Thread-A").start();
Thread threadB = new Thread(() -> multiThreadLock.threadBProcess(), "Thread-B");
threadB.start();
// 可能會(huì)出現(xiàn) Thread-C 先執(zhí)行的情況,所以先通過 join 方法讓線程 B 先跑完
threadB.join();
new Thread(()-> multiThreadLock.threadCProcess(), "Thread-C").start();
}
}
注意:斷點(diǎn)模式下,要以 Thread 模式執(zhí)行;如下圖:
公平鎖、非公平鎖區(qū)別
鎖的公平與否其實(shí)取決于獲取鎖的順序性,若為公平鎖,那么獲取鎖的順序應(yīng)該絕對(duì)符合 FIFO 隊(duì)列 > 先進(jìn)先出的特性,上面所分析的例子都是以非公平鎖(默認(rèn)是非公平鎖)只要 CAS 設(shè)置 AQS#state 屬性值成功,就代表當(dāng)前線程獲取到了鎖,而公平鎖不一樣,差異的地方有如下兩點(diǎn):
1、FairSync#lock、NonfairSync#lock
非公平鎖在獲取鎖時(shí),先通過 CAS 操作進(jìn)行鎖搶占,而公平鎖不會(huì)
2、FairSync#tryAcquire、NonfairSync#tryAcquire
兩者方法之間不同之處在于判斷多了一個(gè)條件:hasQueuedPredecessors,也就是說加入了同步隊(duì)列中當(dāng)前節(jié)點(diǎn)是否有前驅(qū)節(jié)點(diǎn)的判斷,若該方法返回 true,則表示有線程比當(dāng)前線程更早入隊(duì)、更早地請(qǐng)求獲取鎖,因此,需要等待前驅(qū)節(jié)點(diǎn)的線程獲取完再釋放鎖以后才能繼續(xù)獲取鎖!
public final boolean hasQueuedPredecessors() {
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
1、h != t:頭尾節(jié)點(diǎn)是否相同,若相同則表示隊(duì)列中只有一個(gè)節(jié)點(diǎn),即當(dāng)前未發(fā)生鎖競(jìng)爭(zhēng)
假設(shè):當(dāng)前只有線程 Thread-A 一人爭(zhēng)搶鎖,那么 head == null && tail ==null,那么返回 false 會(huì)去爭(zhēng)搶鎖;反之,會(huì)繼續(xù)走第二步的判斷
2、(s = h.next) == null:檢查頭節(jié)點(diǎn)的后繼節(jié)點(diǎn)是否為空,即判斷是否存在后繼節(jié)點(diǎn)
假設(shè):線程 Thread-A、Thread-B 同時(shí)爭(zhēng)搶鎖,Thread-A 搶到了,那么同步等待隊(duì)列中會(huì)有頭節(jié)點(diǎn)、Thread-B 所在節(jié)點(diǎn),條件不滿足返回 false,會(huì)繼續(xù)走第三步的判斷;反之,當(dāng)前只有一個(gè)節(jié)點(diǎn) > 返回 true 不會(huì)去爭(zhēng)搶鎖,不走第三步的判斷
3、s.thread != Thread.currentThread():檢查后繼節(jié)點(diǎn)的線程是否與當(dāng)前線程不同,即判斷后繼節(jié)點(diǎn)持有線程是否為當(dāng)前線程
假設(shè):頭節(jié)點(diǎn)的后繼節(jié)點(diǎn)持有線程就是當(dāng)前的線程,會(huì)返回 false 會(huì)去爭(zhēng)搶鎖;反之,頭節(jié)點(diǎn)的后繼節(jié)點(diǎn)持有線程不是當(dāng)前的線程,會(huì)返回 true 不會(huì)去爭(zhēng)搶鎖,它會(huì)進(jìn)入到排隊(duì)模式?。?!
總結(jié)
ReentrantLock 基于悲觀鎖實(shí)現(xiàn)(LockSupport),但是在處理 AQS#state 鎖狀態(tài)時(shí)是基于 CAS 樂觀鎖實(shí)現(xiàn)的,兩者在不同場(chǎng)景下都會(huì)各自的好處,因?yàn)榍罢咭呀?jīng)悲觀鎖,后者再用 CAS 操作并沒有任何問題
>在這里其實(shí)就是偷換概念了,不一定用了悲觀鎖就不能用樂觀鎖
該篇博文介紹了 JUC 組件下 ReentrantLock 核心概念、使用、源碼以及 AQS 基礎(chǔ)組件的核心方法,闡述了 AQS 內(nèi)部實(shí)現(xiàn)、數(shù)據(jù)結(jié)構(gòu)以及節(jié)點(diǎn)變更過程,在后面,看 ReentrantLock 是如何基于 AQS 核心方法去完成其內(nèi)部鎖競(jìng)爭(zhēng)工作的、鎖釋放后如何喚醒其他節(jié)點(diǎn)線程,全文上下以畫圖+文字加以說明,不限于時(shí)序圖、結(jié)構(gòu)圖、流程圖,最后說明了在 ReentrantLock 公平鎖、非公平鎖之間的區(qū)別,希望能夠幫助你快速理解 AQS 內(nèi)部如何巧妙處理高并發(fā)場(chǎng)景問題的
如果覺得博文不錯(cuò),關(guān)注我 vnjohn,后續(xù)會(huì)有更多實(shí)戰(zhàn)、源碼、架構(gòu)干貨分享!
推薦專欄:Spring、MySQL,訂閱一波不再迷路文章來源:http://www.zghlxwxcb.cn/news/detail-501727.html
大家的「關(guān)注?? + 點(diǎn)贊?? + 收藏?」就是我創(chuàng)作的最大動(dòng)力!謝謝大家的支持,我們下文見!文章來源地址http://www.zghlxwxcb.cn/news/detail-501727.html
到了這里,關(guān)于深入源碼解析 ReentrantLock、AQS:掌握 Java 并發(fā)編程關(guān)鍵技術(shù)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!