一 API 閱讀
一種可重入的互斥鎖为障。擁有和 synchronized 關(guān)鍵字相同的功能,除此之外嚣鄙,也有一定的功能擴(kuò)展吻贿。
一個(gè) ReentrantLock 鎖會(huì)被成功調(diào)用了 lock 方法,且還沒有 unlock 的線程持有拗慨。檢查一個(gè)線程是否持有鎖的方法是 isHeldByCurrentThread 和 getHoldCount廓八。
構(gòu)造函數(shù)可以包含一個(gè)可選的 boolean 值,表示構(gòu)建的鎖是一個(gè) 公平鎖 還是
非公平鎖赵抢。使用默認(rèn)的無參構(gòu)造時(shí)剧蹂,這個(gè)參數(shù)默認(rèn)為 false 即非公平鎖。當(dāng)入?yún)?true 的時(shí)候烦却,表示這是一個(gè)公平鎖宠叼,排隊(duì)的隊(duì)列里等待最久的線程最先獲得鎖。傳入?yún)?shù)為 false 的時(shí)候表示這是一個(gè)非公平鎖其爵,不會(huì)遵循公平鎖里線程獲取鎖的策略冒冬。在競爭線程較多的情況下,使用公平鎖會(huì)導(dǎo)致較低的吞入量摩渺。
需要注意的是简烤,不定期地調(diào)用 tryLock 方法,會(huì)讓爭用線程不遵循公平鎖的競爭模式摇幻。當(dāng)恰巧鎖資源被釋放横侦,而還有排隊(duì)線程的時(shí)候挥萌,主動(dòng)調(diào)用方法可能會(huì)成功提前獲取到鎖。
使用 ReentrantLock 的常見慣例如下
class X {
private final ReentrantLock lock = new ReentrantLock();
public void m() {
lock.lock();
try {
// ... method body
} finally {
lock.unlock();
}
}
}
作為一個(gè)可重入鎖枉侧,ReentrantLock 允許同一個(gè)線程的重入次數(shù)為 Integer.MAX_VALUE引瀑。
二 部分代碼閱讀
看這部分的代碼的時(shí)候,需要結(jié)合前面的文章 AQS 部分一起來看榨馁。
2.1 非公平鎖的 lock 流程
private final Sync sync;
需要注意憨栽,這里這個(gè)成員變量 sync 是 reentrantLock 實(shí)現(xiàn)同步機(jī)制的核心類。因?yàn)?reentrantLock 使用的是 AQS 同步框架翼虫,而 sync 就是這個(gè) AQS 的內(nèi)部實(shí)現(xiàn)類屑柔。
這里 sync 的實(shí)際實(shí)現(xiàn),在 reentrantLock 里面分成了兩大類蛙讥。一個(gè)是公平鎖實(shí)現(xiàn)锯蛀,另一個(gè)是非公平鎖實(shí)現(xiàn)。這里的編碼 遵循了單一職責(zé)原則次慢,也符合 AQS 同步器框架的推薦做法。
當(dāng)我們使用默認(rèn)的無參構(gòu)造函數(shù)創(chuàng)建一個(gè) reentrantLock 實(shí)例翔曲。然后調(diào)用 lock() 方法迫像,其流程如下:
實(shí)際調(diào)用的方法就是這里的
java.util.concurrent.locks.ReentrantLock.NonfairSync#lock
final void lock() {
// cas 方式更新 AQS 的 state 成員值 +1
if (compareAndSetState(0, 1))
// 更新成功,設(shè)置獨(dú)占鎖線程引用為當(dāng)前線程
setExclusiveOwnerThread(Thread.currentThread());
else
// cas 更新失敗瞳遍,調(diào)用 AQS 的 acquire 方法
acquire(1);
}
先嘗試直接修改 AQS 內(nèi)部維護(hù)的 state 成員變量闻妓,0 表示沒有線程持有鎖,由 CAS 方式更新為 1掠械。如果更新成功由缆,即表示當(dāng)前線程成功持有了這個(gè)可重入獨(dú)占鎖,這時(shí)更新一下獨(dú)占鎖的線程引用為當(dāng)前線程猾蒂。
如果 cas 方式更新 state 字段失敗均唉,那么就調(diào)用 AQS 內(nèi)定義的 acquire 方法來嘗試獲取鎖。這個(gè)方法之前在 AQS 源碼閱讀的時(shí)候詳細(xì)讀過肚菠。通過定義一套模板方法舔箭,來實(shí)現(xiàn)加鎖操作。其中的方法
- acquireQueued
- addWaiter
都是 AQS 自己實(shí)現(xiàn)蚊逢,子類需要補(bǔ)充的方法是
- tryAcquire
在內(nèi)部類
java.util.concurrent.locks.ReentrantLock.NonfairSync
中层扶,這個(gè)方法的實(shí)現(xiàn)指向了
java.util.concurrent.locks.ReentrantLock.NonfairSync#tryAcquire
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
nonfairTryAcquire 代碼如下:
final boolean nonfairTryAcquire(int acquires) {
// 獲取當(dāng)前線程
final Thread current = Thread.currentThread();
// 獲取 AQS 內(nèi)成員變量 state
int c = getState();
// 如果 state 為 0,表示鎖空閑烙荷,嘗試獲取鎖
if (c == 0) {
// cas 方式更新 state 字段
if (compareAndSetState(0, acquires)) {
// 更新成功镜会,設(shè)置當(dāng)前線程引用為
setExclusiveOwnerThread(current);
return true;
}
}
// state 不為 0,表示鎖已經(jīng)被某線程持有终抽,先檢查是不是自己持有
else if (current == getExclusiveOwnerThread()) {
// ReentrantLock 支持重入戳表,所以累加 acquire 值
int nextc = c + acquires;
// 檢查重入次數(shù)有沒有溢出焰薄,溢出則拋出異常
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
// 未溢出,更新 state 值
setState(nextc);
return true;
}
// 獲取鎖失敗扒袖,返回false
return false;
}
todo nonfairTryAcquire 流程圖
如果此處的 nonfairTryAcquire 方法加鎖失敗塞茅,那么嘗試加鎖的線程會(huì)被加入同步隊(duì)列排隊(duì)(即 AQS 的 addWaiter 和 acquireQueued 方法)。而這個(gè)同步隊(duì)列的排隊(duì)喚醒線程機(jī)制又是默認(rèn)的 非公平鎖 機(jī)制季率。
至此野瘦,我們應(yīng)該知道的是,reentrantLock 的非公平鎖核心機(jī)制是依賴于 AQS 的內(nèi)容實(shí)現(xiàn)的飒泻。reentrantLock 本身也沒有維護(hù)線程等待隊(duì)列鞭光,這是 AQS 的工作。reentrantLock 只是通過內(nèi)部類來實(shí)現(xiàn)了這個(gè)功能泞遗。
2.2 公平鎖的 lock 流程
當(dāng)以如下的方式聲明一個(gè) reentrantLock 對象時(shí)惰许,我們就可以得到一個(gè)公平鎖。
ReentrantLock lock = new ReentrantLock(Boolean.TRUE);
公平鎖和非公平鎖的區(qū)別在于:排隊(duì)線程的獲取鎖時(shí)機(jī)是有順序的史辙,等待最久的線程最先獲得鎖汹买。
與默認(rèn)的 NoFairSync 實(shí)現(xiàn)相比,其他的都一樣聊倔,主要的區(qū)別在自己實(shí)現(xiàn)的 tryAcquire 方法晦毙。
java.util.concurrent.locks.ReentrantLock.FairSync#tryAcquire
// 公平鎖版本的 tryAcquire
protected final boolean tryAcquire(int acquires) {
// 獲取當(dāng)前線程
final Thread current = Thread.currentThread();
// 獲取 AQS 同步器維護(hù)的鎖狀態(tài)字段 state
int c = getState();
// c == 0 表示當(dāng)前鎖處于空閑狀態(tài),可以嘗試獲取鎖
if (c == 0) {
// hasQueuedPredecessors 方法用于判斷當(dāng)前嘗試獲取鎖的線程是否需要排隊(duì)耙蔑,如果不需要排隊(duì)則直接更新 state 字段并設(shè)置獨(dú)占線程的引用见妒,在判斷體內(nèi)返回 true
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 走到這里 c != 0,即鎖已被占有甸陌,檢查持有鎖的線程是不是當(dāng)前線程自己
else if (current == getExclusiveOwnerThread()) {
// 是當(dāng)前線程持有鎖须揣,增加重入加鎖次數(shù),傳入的 acquires 為 1
int nextc = c + acquires;
// 重入次數(shù)超過 Integer.MAX_VALUE 溢出為負(fù)數(shù)钱豁,拋出異常
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
// 未溢出耻卡,設(shè)置更新 state 字段值
setState(nextc);
// 返回 true
return true;
}
// 嘗試加鎖失敗,返回 false
return false;
}
}
公平鎖與非公平鎖的 tryAcquire 方法寥院,主要區(qū)別在一個(gè)地方
hasQueuedPredecessors
當(dāng)鎖處于空閑狀態(tài)時(shí)劲赠,公平鎖加鎖的前置判斷條件多了這么一個(gè)方法。
在 state = 0 的條件下秸谢,非公平鎖內(nèi)的線程不用檢查 AQS 維護(hù)點(diǎn)隊(duì)列信息而直接嘗試爭用鎖凛澎;
public final boolean hasQueuedPredecessors() {
// The correctness of this depends on head being initialized
// before tail and on head.next being accurate if the current
// thread is first in queue.
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());
}
拿到 AQS 維護(hù)的線程等待隊(duì)列的頭節(jié)點(diǎn)/尾節(jié)點(diǎn)引用。然后有一個(gè)嵌套的判斷邏輯估蹄,返回 false 表示可以直接加鎖塑煎,返回 true 的時(shí)候就需要入隊(duì)。
第一個(gè)條件 A臭蚁,頭節(jié)點(diǎn)不等于尾節(jié)點(diǎn)最铁,即隊(duì)列中還有在排隊(duì)的線程讯赏。如果這個(gè)條件不滿足(即頭節(jié)點(diǎn)等于尾節(jié)點(diǎn)),說明隊(duì)列中無排隊(duì)線程冷尉,可以直接入隊(duì)漱挎,不需要將現(xiàn)有線程入隊(duì)。此時(shí)觸發(fā)短路邏輯雀哨,直接返回 false磕谅。
第二個(gè)條件組 B,兩個(gè)條件滿足一個(gè)即可
- 頭節(jié)點(diǎn)的后繼節(jié)點(diǎn)不為空
- 頭節(jié)點(diǎn)的后繼節(jié)點(diǎn)不是當(dāng)前嘗試獲取鎖的節(jié)點(diǎn)雾棺,如果這條為 false膊夹,表示排隊(duì)里下一個(gè)即將拿到鎖的線程就是當(dāng)前線程
在條件 A 返回 true 的情況下:
當(dāng)這兩個(gè)判斷 B1,B2 同時(shí)為 false捌浩,表示同步隊(duì)列有排隊(duì)線程放刨,并且同步隊(duì)列里排隊(duì)最靠前都線程就是當(dāng)前線程,這個(gè)時(shí)候也就 不需要排隊(duì)尸饺, 直接獲取进统。
B1 返回 true,這個(gè)時(shí)候同步隊(duì)列正處在初始化過程中侵佃,此時(shí)觸發(fā)了條件組 B 的短路邏輯麻昼。整個(gè)條件組 B 返回 true。說明已經(jīng)有其他線程在當(dāng)前線程之前爭用鎖了馋辈,那么當(dāng)前線程 需要排隊(duì) 。整個(gè)判斷邏輯返回 false倍谜。
B1 返回 false迈螟,B2 返回 true。表示同步隊(duì)列正在初始化過程中尔崔,并且排隊(duì)等待的下一個(gè)線程不是當(dāng)前線程答毫,那當(dāng)前線程依舊需要 加入排隊(duì)隊(duì)列 等候。
2.3 unlock 流程
公平鎖和非公平鎖的釋放鎖流程都是一樣的季春。當(dāng)我們調(diào)用
reentrantLock.unlock()
方法洗搂,debug 源代碼,可以看到還是使用了實(shí)現(xiàn)了 AQS 內(nèi)部類的成員變量的釋放鎖方法载弄。
public void unlock() {
sync.release(1);
}
而對應(yīng)的 release 方法的代碼如下耘拇,這個(gè)模板方法依然是在 AQS 同步器內(nèi)。
public final boolean release(int arg) {
// 嘗試釋放鎖
if (tryRelease(arg)) {
// 獲取頭節(jié)點(diǎn)
Node h = head;
// 頭節(jié)點(diǎn)不為空且頭節(jié)點(diǎn)的節(jié)點(diǎn)狀態(tài)不為0(不為0表示這個(gè)節(jié)點(diǎn)不是初始化虛擬節(jié)點(diǎn))
if (h != null && h.waitStatus != 0)
// 修改節(jié)點(diǎn) status 字段并喚醒等待線程
unparkSuccessor(h);
return true;
}
// 釋放鎖失敗宇攻,返回 false
return false;
}
tryRelease 方法和之前的 tryAcquire 方法一下惫叛,都是需要 AQS 同步器的實(shí)現(xiàn)類自己編寫的部分。
java.util.concurrent.locks.ReentrantLock.Sync#tryRelease
// 內(nèi)部類實(shí)現(xiàn)的 - 嘗試釋放鎖方法逞刷,注意傳入的 releases 值為 1
protected final boolean tryRelease(int releases) {
// 獲取當(dāng)前 state 值嘉涌,然后減 1妻熊,得到一個(gè)釋放鎖之后 state 的期望值
int c = getState() - releases;
// 檢查釋放鎖線程和加鎖線程是不是同一個(gè)線程
if (Thread.currentThread() != getExclusiveOwnerThread())
// 不是的話,直接拋出異常
throw new IllegalMonitorStateException();
boolean free = false;
// 如果 state 期望值為 0仑最,表示沒有重入加鎖扔役,現(xiàn)在可以直接釋放鎖
if (c == 0) {
// 注意只有當(dāng) state 計(jì)數(shù)值為 0 的時(shí)候,才能釋放鎖警医,否則表示之前同一個(gè)線程有重入加鎖操作
free = true;
// 取消獨(dú)占線程的引用
setExclusiveOwnerThread(null);
}
// 更新 state 值
setState(c);
// 返回釋放鎖標(biāo)識位
return free;
}