ReentrantLock 與 AQS 源碼分析
1. 基本結(jié)構(gòu)
?? 重入鎖 ReetrantLock盖袭,JDK 1.5新增的類(lèi)岖沛,作用與synchronized關(guān)鍵字相當(dāng)昭殉,但比synchronized更加靈活胧谈。ReetrantLock本身也是一種支持重進(jìn)入的鎖物赶,即該鎖可以支持一個(gè)線程對(duì)資源重復(fù)加鎖,但是加鎖多少次较坛,就必須解鎖多少次印蔗,這樣才可以成功釋放鎖。
1. 繼承
沒(méi)有繼承任何類(lèi)丑勤,因?yàn)楹芏嗖僮鞫际褂昧私M合完成华嘹。
2. 實(shí)現(xiàn)
Lock, java.io.Serializable
??這里著重介紹一下 Lock 接口,接口定義了幾個(gè)必要的方法确封,也是在 ReentrantLock 中的重點(diǎn)需要分析的方法除呵。
?? 三類(lèi)方法:獲取鎖、釋放鎖爪喘、獲取條件颜曾。
public interface Lock {
// 阻塞獲取鎖,如果獲取不到鎖就一直等待
void lock();
// 可中斷獲取鎖秉剑,在獲取鎖的過(guò)程可以被中斷泛豪,但是 Synchronized 是不可以
void lockInterruptibly() throws InterruptedException;
// 非阻塞獲取鎖,沒(méi)有獲取到鎖立即返回
boolean tryLock();
// 超時(shí)獲取鎖侦鹏,沒(méi)獲取到鎖等待一段時(shí)間
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
// 解鎖
void unlock();
// 等待喚醒機(jī)制的條件
Condition newCondition();
}
從上面可以看到 Synchronized 和 Lock 的一些重要區(qū)別:
Lock 的獲取鎖的過(guò)程是可以中斷的诡曙,Synchronized 不可以,Synchronized 只能在 wait或同步代碼塊執(zhí)行過(guò)程中才可以被中斷略水。
由于 Lock 顯示的加鎖价卤,鎖可以橫跨幾個(gè)方法,也就是臨界區(qū)的位置可以更加自由渊涝。
Lock 支持超時(shí)獲取鎖慎璧。
后面會(huì)看到 Lock 還支持公平及非公平鎖。
綁定多個(gè) Condition 條件
3. 主要字段
??很好跨释,這個(gè)類(lèi)的字段非常的少胸私,真正起作用的字段只有一個(gè) “鎖” 字段。
// 同步鎖
private final Sync sync;
?? 這個(gè)鎖(Sync)是一個(gè)繼承自 AQS 的抽象內(nèi)部類(lèi)鳖谈,說(shuō)明一下 AQS (AbstractQueuedSynchronizer) 一般被稱(chēng)為隊(duì)列同步器岁疼,他是并發(fā)包中的核心組件,絕大多數(shù)鎖機(jī)制都是采用的這個(gè)類(lèi)來(lái)實(shí)現(xiàn)的缆娃。雖然看到他是一個(gè)抽象類(lèi)捷绒,但是你會(huì)發(fā)現(xiàn)里面沒(méi)有一個(gè)方法是抽象方法瑰排,他實(shí)現(xiàn)了鎖機(jī)制中的必要的通用的方法,待會(huì)會(huì)專(zhuān)門(mén)講這個(gè)類(lèi)疙驾。不然 ReentrantLock 沒(méi)辦法說(shuō)凶伙,ReentrantLock 里面的鎖操作都是依賴(lài)于 AQS郭毕。
?? 然后這個(gè)鎖是有兩個(gè)子類(lèi)它碎,分別是 NonfairSync
和 FairSync
從名字上也可以看出這兩個(gè)類(lèi)分別代表了 公平鎖
和 非公平鎖
。何為鎖的公平性显押? 實(shí)際上就是新來(lái)的線程需要征用鎖必須要要等到先于他到達(dá)的線程獲取并釋放鎖扳肛。也就是獲取鎖的過(guò)程是按照下來(lái)后到的順序進(jìn)行的,反之就稱(chēng)為非公平鎖乘碑。后面我們會(huì)看到其實(shí)這兩種鎖不同就在于非公平鎖在新線程創(chuàng)建后首先會(huì)直接進(jìn)行鎖的獲取挖息,如果沒(méi)有獲取到會(huì)進(jìn)行一段時(shí)間的自旋,始終沒(méi)獲取到鎖才進(jìn)行等待狀態(tài)兽肤。
?? 一般而言套腹,公平鎖開(kāi)銷(xiāo)比非公平鎖大,這也是比較符合我們的直觀感受资铡。公平鎖是需要進(jìn)行排隊(duì)的电禀,但在某些場(chǎng)景下,可能更注重時(shí)間先后順序笤休,那么公平鎖自然是很好的選擇尖飞。
?? 好總結(jié)一下,在 ReentrantLock 中只維護(hù)了一個(gè) “鎖” 變量店雅,這個(gè)鎖是繼承了 AQS 同步器政基,然后這個(gè)鎖又有兩種派生的鎖:公平鎖,非公平鎖闹啦。那么 ReentrantLock 實(shí)現(xiàn)其實(shí)就有兩種方式:公平鎖沮明,非公平鎖。
4. 主要方法概覽
- ctor-2
- lock
- lockInterruptibly
- tryLock
- tryLock(time)
- unlock
- newCondition
2. 基礎(chǔ)并發(fā)組件 AQS
1. 基本字段
1. 重要字段
?? AQS 是維護(hù)了一個(gè)同步隊(duì)列(雙向鏈表)窍奋,這個(gè)隊(duì)列里面線程都是需要競(jìng)爭(zhēng)鎖的荐健,沒(méi)有競(jìng)爭(zhēng)到的就在同步隊(duì)列中等待。head
和 tail
就指向隊(duì)列的首尾费变。state
是一個(gè)標(biāo)志字段摧扇,表示當(dāng)前有多少線程在臨界區(qū)。一般來(lái)說(shuō) state
只能是 0 或 1 但是由于鎖是可重入的挚歧,所以也有大于 1 的情況扛稽。
?? 除了一個(gè)同步隊(duì)列還有 0~n 個(gè)等待隊(duì)列,等待隊(duì)列就是調(diào)用了 await
方法的線程滑负,會(huì)被掛到調(diào)用了 await
的 condition
上面的等待隊(duì)列在张,所以有多少個(gè) condition
就有多少等待隊(duì)列用含。
//同步隊(duì)列頭指針
private transient volatile Node head;
// 同步隊(duì)列尾指針
private transient volatile Node tail;
// 狀態(tài)標(biāo)志,0 則沒(méi)有線程在臨界區(qū)帮匾,非零表示有 state 個(gè)線程在臨界區(qū)(由于鎖可重入)
private volatile int state;
2. Node 節(jié)點(diǎn)
??Node 節(jié)點(diǎn)也就是上文所提到的 同步隊(duì)列
和 等待隊(duì)列
中的元素啄骇,注意兩個(gè)隊(duì)列之間的元素類(lèi)型是一樣的因?yàn)樗麄冎g會(huì)有相互移動(dòng)轉(zhuǎn)換的動(dòng)作,這兩個(gè)隊(duì)列中的元素自然是線程瘟斜,為了方便查找和表示 AQS 將線程封裝到了 Node 節(jié)點(diǎn)中缸夹,構(gòu)成雙向隊(duì)列。
static final class Node {
// 共享非 null/獨(dú)占為 null
static final Node SHARED = new Node();
static final Node EXCLUSIVE = null;
/**
* 線程狀態(tài)
*/
static final int CANCELLED = 1;
static final int SIGNAL = -1;
static final int CONDITION = -2;
static final int PROPAGATE = -3;
volatile int waitStatus;
// 雙向鏈表 這兩個(gè)指針用于同步隊(duì)列構(gòu)建鏈表使用的 下面還有一個(gè) nextWaiter 是用來(lái)構(gòu)建等待單鏈表隊(duì)列
volatile Node prev;
volatile Node next;
// 線程
volatile Thread thread;
// 等待隊(duì)列單鏈表
Node nextWaiter;
/**
* Returns true if node is waiting in shared mode.
*/
final boolean isShared() {
return nextWaiter == SHARED;
}
}
?? 可以看到上面有一個(gè) waitStatus
屬性螺句,代表了線程當(dāng)前的狀態(tài)虽惭,狀態(tài)標(biāo)識(shí)就是那些常量。具體如下:
SIGNAL: 正在執(zhí)行的線程結(jié)束釋放鎖或者被取消執(zhí)行蛇尚,他必須喚醒后續(xù)的狀態(tài)為 SIGNAL 節(jié)點(diǎn)
CANCELLED: 在同步隊(duì)列中等待的線程等待超時(shí)或被中斷芽唇,需要從同步隊(duì)列中取消該Node的結(jié)點(diǎn), 其結(jié)點(diǎn)的waitStatus為CANCELLED取劫,即結(jié)束狀態(tài)匆笤,進(jìn)入該狀態(tài)后的結(jié)點(diǎn)將不會(huì)再變化。
CONDITION: 該標(biāo)識(shí)的結(jié)點(diǎn)處于等待隊(duì)列中(不是同步隊(duì)列)谱邪,結(jié)點(diǎn)的線程等待在Condition上炮捧,當(dāng)其他線程調(diào)用了Condition的signal()方法后,CONDITION狀態(tài)的結(jié)點(diǎn)將從等待隊(duì)列轉(zhuǎn)移到同步隊(duì)列中虾标,等待獲取同步鎖寓盗。
PROPAGATE:在共享模式中,該狀態(tài)標(biāo)識(shí)結(jié)點(diǎn)的線程處于可運(yùn)行狀態(tài)璧函。
0:代表初始化狀態(tài)傀蚌。
?? 可以看到,Node 里面的主要字段就是一個(gè)狀態(tài)標(biāo)志位蘸吓、一個(gè)線程的引用善炫、用于構(gòu)建鏈表的指針。注意库继,有三個(gè)指針箩艺,其中前兩個(gè) next
和 pre
是用來(lái)構(gòu)建同步隊(duì)列的(雙向鏈表),后面 nextWaiter
是用來(lái)構(gòu)建等待隊(duì)列宪萄。所以說(shuō)雖然同步隊(duì)列和等待隊(duì)列使用的同一個(gè)數(shù)據(jù)類(lèi)型艺谆,數(shù)據(jù)結(jié)構(gòu)是不同的,并且在后面我們會(huì)看到等待隊(duì)列中的節(jié)點(diǎn)只有兩種狀態(tài) Condition
和 CANCELLED
前者表示線程已結(jié)束需要從等待隊(duì)列中移除拜英,后者表示條件結(jié)點(diǎn)等待被喚醒静汤。
??下面畫(huà)圖說(shuō)明一下同步隊(duì)列和等待隊(duì)列的情況。
等待隊(duì)列
同步隊(duì)列
3. ConditionObject
?? 這個(gè)內(nèi)部類(lèi)是等待喚醒機(jī)制的核心,在他上面綁定了一個(gè)等待隊(duì)列虫给。在這個(gè)類(lèi)中使用了兩個(gè)指針( firstWaiter/lastWaiter
)指向隊(duì)列的首尾藤抡。這里主要看一下 await
、signal
和 signalAll
方法抹估。
- await
?? 當(dāng)一個(gè)線程調(diào)用了await()相關(guān)的方法缠黍,那么首先構(gòu)建一個(gè)Node節(jié)點(diǎn)封裝當(dāng)前線程的相關(guān)信息加入到等待隊(duì)列中進(jìn)行等待,并釋放鎖直到被喚醒(移動(dòng)到同步隊(duì)列)药蜻、中斷瓷式、超時(shí)才被隊(duì)列中移出。被喚醒后的第一件事是搶鎖和檢查是否被中斷谷暮,然后才是移除隊(duì)列蒿往。被喚醒時(shí)候的狀態(tài)應(yīng)該為 SIGNAL ,而在方法中執(zhí)行的移除隊(duì)列的操作就是移除狀態(tài)非 Condition 的節(jié)點(diǎn)湿弦。
public final void await() throws InterruptedException {
// 等待可中斷
if (Thread.interrupted())
throw new InterruptedException();
// 加入等待隊(duì)列, new 新的 Node 做一個(gè)尾插入
Node node = addConditionWaiter();
// 釋放當(dāng)前線程的鎖腾夯,失敗則將當(dāng)前線程設(shè)置為取消狀態(tài)
int savedState = fullyRelease(node);
int interruptMode = 0;
// 如果沒(méi)在同步隊(duì)列就讓線程等待也就是看是否被喚醒
// 如果有中斷或者被喚醒那么退出循環(huán)
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
// 運(yùn)行到此處說(shuō)明已經(jīng)被喚醒了颊埃,因?yàn)榻Y(jié)束了循環(huán)
// 喚醒后,首先自旋一下獲取鎖蝶俱,同時(shí)判斷是否中斷
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
// 清理隊(duì)列中狀態(tài)不是 Condition 的的任務(wù)班利,包括被喚醒的 SIGNAL 和 被取消的 CANCELLED
if (node.nextWaiter != null)
unlinkCancelledWaiters();
//被中斷 拋異常
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
- signal/doSignal/signalAll
?? 執(zhí)行 signal 首先進(jìn)行鎖的判斷,如果沒(méi)有獲取到獨(dú)占鎖就直接拋出異常榨呆。這也就是為什么只有擁有鎖的線程才能執(zhí)行 signal 罗标,然后獲取等待隊(duì)列中的第一個(gè)節(jié)點(diǎn)執(zhí)行 doSignal。
public final void signal() {
// 獲取獨(dú)占鎖
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
// 喚醒等待隊(duì)里中的第一個(gè)線程
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
?? doSignal 方法主要就干了三個(gè)事 :
- 將被喚醒的節(jié)點(diǎn)從等待隊(duì)列中移除(while 循環(huán)體)积蜻,如果被喚醒的節(jié)點(diǎn)被取消了就繼續(xù)喚醒后面的節(jié)點(diǎn)(transferForSignal 返回 false)
- 否則把這個(gè)節(jié)點(diǎn)加入到同步隊(duì)列 ( enq 方法 )
- 當(dāng)同步隊(duì)列中當(dāng)前節(jié)點(diǎn)的前驅(qū)被取消或者沒(méi)辦法喚醒時(shí)則喚醒這個(gè)線程 ( unpark )闯割,這時(shí)候調(diào)用了 unpark 正好和 await 中的 park 相對(duì)應(yīng)使得 await 的線程被喚醒,接著執(zhí)行循環(huán)體判斷自己已經(jīng)被移入到同步隊(duì)列了竿拆,接著就可以執(zhí)行后面的獲取鎖的操作宙拉。
private void doSignal(Node first) {
do {
// 頭指針指向喚醒節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn),并順便判斷等待隊(duì)列是否空
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
// 解除引用
first.nextWaiter = null;
} while (!transferForSignal(first) && (first = firstWaiter) != null); //移入同步隊(duì)列失敗則繼續(xù)喚醒下一個(gè)線程丙笋,否則喚醒成功
// 喚醒成功的線程不一定馬上能開(kāi)始執(zhí)行谢澈,只有在前驅(qū)節(jié)點(diǎn)被取消或者沒(méi)辦法被喚醒時(shí)
}
// 將節(jié)點(diǎn)從等待隊(duì)列移動(dòng)到同步隊(duì)列 成功返回 true 失敗 false
final boolean transferForSignal(Node node) {
// 在等待隊(duì)列中的節(jié)點(diǎn)只有 condition 和 cancelled 兩種狀態(tài),如果狀態(tài)更新失敗說(shuō)明任務(wù)被取消
// 否則更新為初始狀態(tài) 直接返回的話上面的 doSignal 就會(huì)繼續(xù)喚醒后面的線程
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
// 把當(dāng)前節(jié)點(diǎn)加入同步隊(duì)列
Node p = enq(node);
// 獲取同步隊(duì)列中倒數(shù)第二個(gè)節(jié)點(diǎn)的狀態(tài)御板,當(dāng)前節(jié)點(diǎn)的前驅(qū)
int ws = p.waitStatus;
// 如果前驅(qū)節(jié)點(diǎn)被取消或者在設(shè)置前驅(qū)節(jié)點(diǎn)狀態(tài)為Node.SIGNAL狀態(tài)失敗時(shí)锥忿,喚醒被通知節(jié)點(diǎn)代表的線程
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
// 插入一個(gè)節(jié)點(diǎn)到同步隊(duì)列,如果同步隊(duì)列是空的則加入一個(gè)空節(jié)點(diǎn)做為頭結(jié)點(diǎn)
// 死循環(huán)保證肯定能插入 返回插入節(jié)點(diǎn)的前驅(qū)
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
// 這一步不需要 cas 是因?yàn)椴l(fā)沒(méi)關(guān)系怠肋,只是指向鏈表結(jié)尾敬鬓,不會(huì)多線程更新問(wèn)題
node.prev = t;
// 可能有多個(gè)線程搶
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
?? 有一個(gè)小問(wèn)題,就是在某個(gè)線程中執(zhí)行了別人的 signal 不會(huì)導(dǎo)致當(dāng)前線程立即放棄鎖,之所以會(huì)這樣正是由于 ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL)
這個(gè)判斷,即前驅(qū)線程都結(jié)束了列林。比如下面的例子:
package util.AQSTest;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
// test signal 執(zhí)行后不會(huì)導(dǎo)致當(dāng)前線程立即釋放鎖
public class AQSTest {
static Lock lock = new ReentrantLock();
static Condition run1Cond = lock.newCondition();
static Condition run2Cond = lock.newCondition();
static class Runner1 implements Runnable {
@Override
public void run() {
lock.lock();
try {
System.out.println("runner 1 start");
run1Cond.await(1, TimeUnit.SECONDS);
run2Cond.signal();
System.out.println("runner 1 exit");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
static class Runner2 implements Runnable {
@Override
public void run() {
lock.lock();
try {
System.out.println("runner 2 start");
run2Cond.await();
System.out.println("runner 2 exit");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
new Thread(new Runner1(),"runner1").start();
new Thread(new Runner2(),"runner2").start();
}
}
輸出的結(jié)果始終是:
runner 1 start
runner 2 start
runner 1 exit
runner 2 exit
?? 我使用了工具對(duì)上面的代碼進(jìn)行了調(diào)試瑞你,大致說(shuō)一下流程,順便用來(lái)捋一捋等待喚醒機(jī)制希痴。
?? 首先 runner1 啟動(dòng)者甲,獲取到鎖,打印出 “runner1 start” 砌创,然后調(diào)用了 await 方法虏缸,此時(shí) runner1 線程就執(zhí)行了 AQS 中的 ConditionObject 中的 await 方法,該方法首先 new 了一個(gè)新的節(jié)點(diǎn)嫩实,把 runner1 封裝到這個(gè)節(jié)點(diǎn)里面刽辙。掛在了 run1Con 的等待隊(duì)列上,然后執(zhí)行了釋放鎖并判斷中斷甲献。緊接著 runner1 線程執(zhí)行循環(huán)體判斷是否被喚醒也就是是否在同步隊(duì)列宰缤,顯然這時(shí)候不在,就直接調(diào)用了 park 方法晃洒,執(zhí)行休眠 1 秒鐘操作慨灭, park 方法是 native 方法由操作系統(tǒng)實(shí)現(xiàn)。在上面線程釋放鎖的時(shí)候執(zhí)行的操作是 fullyRelease
這個(gè)方法調(diào)用了 release
方法球及,而 release
方法中釋放了鎖之后氧骤,會(huì)檢查同步隊(duì)列中是否還有以前因?yàn)闆](méi)搶到鎖而等待的線程,如果有執(zhí)行 unparkSuccessor
也就是喚醒同步隊(duì)列中的后繼線程吃引。那么此時(shí) runner2 會(huì)被喚醒筹陵,喚醒后就去搶鎖,獲取到 lock 鎖后輸出了 “runner2 start” 镊尺,然后 runner2 線程又會(huì)因?yàn)檎{(diào)用 await
處于和 runner1 同樣的境地朦佩,也就是被放入 run2Con 的等待隊(duì)列。好鹅心!此時(shí) runner1 的超時(shí)時(shí)間到了吕粗,就會(huì)被 unpark 這個(gè) unpark 是被操作系統(tǒng)調(diào)用的,之后繼續(xù)執(zhí)行循環(huán)體發(fā)現(xiàn)超時(shí)時(shí)間小于等于 0 旭愧,則調(diào)用 transferAfterCancelledWait
里面調(diào)用了 enq
就是加入同步隊(duì)列颅筋,接著開(kāi)始競(jìng)爭(zhēng)鎖,開(kāi)始執(zhí)行 run2Con 上的 signal 此時(shí) signal 調(diào)用 doSignal 先執(zhí)行 do while 中的循環(huán)體输枯,runner2 從 run2Con 的等待隊(duì)列上移除议泵,然后執(zhí)行 transferForSignal
里面又調(diào)用了 enq
將他加入同步隊(duì)列,并返回同步隊(duì)列中的前驅(qū)桃熄,前驅(qū)節(jié)點(diǎn)狀態(tài)不是 Cancelled 或者 可以被置為 SIGNAL 則 signal 方法結(jié)束先口。接著打印了 “runner1 exit” 。接著需要執(zhí)行 finally 里面的釋放鎖的操作了,顯然 unlock 肯定調(diào)用了 release 碉京,而 release 會(huì)喚醒同步隊(duì)列中的后繼的線程厢汹,那么位于同步隊(duì)列中的 runner2 之前的 park 狀態(tài)就會(huì)被打斷,從而跳出 while 循環(huán)谐宙,執(zhí)行獲取鎖的操作烫葬。打印出 “runner2 exit” ,最后釋放鎖整個(gè)程序結(jié)束凡蜻。
?? 現(xiàn)在總算是吧 Condition 的等待喚醒機(jī)制弄清楚了搭综。也把 AQS 中的兩個(gè)內(nèi)部類(lèi)的功能都解釋完了。接下來(lái)就看 AQS 中的方法划栓。
2. 重要方法
- get/setState
- release/tryRelease/unparkSuccessor/fullyRelease
- acquire/tryAcquire/addWaiter/tryQueued
- acquireShared
- releaseShared
?? 這些屬于 AQS 中常用的方法兑巾,但是里面的核心方法都是模板方法,也就是說(shuō)由繼承他的子類(lèi)來(lái)實(shí)現(xiàn)忠荞,所以只能看個(gè)大概的邏輯蒋歌。一會(huì)等到講 ReentrantLock 時(shí)再詳細(xì)說(shuō)這里面的方法。
3. ReentrantLock 內(nèi)部類(lèi) Sync/fairSync/noFairSync
1. Sync
?? 這三個(gè)內(nèi)部類(lèi)實(shí)際上是繼承自 AQS 钻洒,也就是說(shuō) ReentrantLock 是采用了 AQS 作為自己的核心并發(fā)控制組件完成的一系列的鎖操作奋姿,及等待喚醒機(jī)制。
?? 首先看一下 Sync 他是后面兩個(gè)的父類(lèi)素标,他直接繼承自 AQS 。AQS 中留了幾個(gè)比較重要的模板方法 tryAcquire 萍悴、tryRelease 头遭。這個(gè)方法直接實(shí)現(xiàn)了一些在公平鎖和非公平鎖中的通用操作,也就是釋放鎖的操作 tryRelease 癣诱。
?? tryRelease 的實(shí)現(xiàn)很簡(jiǎn)單计维,主要就是依賴(lài)于 AQS 中的 state 屬性,如果state 值減去要釋放的信號(hào)量為 0 則釋放成功撕予,否則失敗鲫惶。
// 釋放鎖的公共操作
protected final boolean tryRelease(int releases) {
// 釋放鎖首先就是使用 AQS 中的 state 的值減去信號(hào)量 判斷是否為0
// 如果是 0 則表明成功釋放鎖,獨(dú)占線程設(shè)為 null实抡,否則說(shuō)明還占用鎖
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;
}
2. fairSync
   公平鎖執(zhí)行 lock 操作就是執(zhí)行了 AQS 中的 acquire(1) 也就是請(qǐng)求一個(gè)鎖資源。但是注意吆寨,在 AQS 中的 acquire 中的 tryAcquire 方法沒(méi)有實(shí)現(xiàn)赏淌,所以必須由當(dāng)前類(lèi)實(shí)現(xiàn)。
   在 tryAcquire 中做的事情就是看是否有代碼在臨界區(qū)啄清。沒(méi)有則還要看同步隊(duì)列中是否有線程等待六水,當(dāng)只有這一個(gè)線程在獲取鎖的時(shí)候才能正常的獲取鎖,其他情況都失敗。
// 公平鎖
static final class FairSync extends Sync {
final void lock() {
acquire(1);
}
// 沒(méi)有代碼在臨界區(qū)或者是當(dāng)前線程的重入 則獲取成功掷贾,否則失敗
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// 如果當(dāng)前線程在獲取鎖的過(guò)程沒(méi)有其他線程在臨界區(qū)
if (c == 0) {
// 如果同步隊(duì)列中沒(méi)有等待的線程睛榄,就設(shè)置 state ,并且當(dāng)前線程設(shè)為獨(dú)占線程
if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 有程序在臨界區(qū)想帅,如果是當(dāng)前線程可重入场靴,加上請(qǐng)求的資源數(shù)
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
// 競(jìng)爭(zhēng)鎖失敗,因?yàn)樗枪降逆i競(jìng)爭(zhēng)
return false;
}
}
3. noFairSync
同理博脑,這個(gè)方法也需要實(shí)現(xiàn) lock 和 tryAcquire 操作憎乙。在 lock 中直接判斷是否有代碼在臨界區(qū),沒(méi)有則直接獲取到鎖叉趣,與公平鎖不同的是:公平鎖還判斷了等待隊(duì)列中是否有等待的線程泞边。有在臨界區(qū)的情況時(shí)執(zhí)行 acquire 操作。同樣的疗杉,首先要執(zhí)行 tryAcquire 如果失敗阵谚,加入同步隊(duì)列并自旋獲取鎖。還是 tryAcquire 的實(shí)現(xiàn)烟具,這里又調(diào)用了 nonfairTryAcquire梢什。
// 非公平鎖
static final class NonfairSync extends Sync {
final void lock() {
// 如果沒(méi)有代碼在臨界區(qū) 直接獲取鎖,獨(dú)占
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
// 有代碼在臨界區(qū)則執(zhí)行嘗試獲取鎖
acquire(1);
}
// 和公平鎖中的 tryAcquire 一模一樣只是少了關(guān)于同步隊(duì)列中是否有等待線程的判斷
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// 沒(méi)有線程獲取鎖 直接獲取到鎖 和公平鎖中的 tryAcquire 一模一樣只是少了關(guān)于同步隊(duì)列的判斷
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 重入鎖
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;
}
?? 好了朝聋,現(xiàn)在我們 AQS 中的空的核心方法也被子類(lèi)實(shí)現(xiàn)了嗡午,那么現(xiàn)在 fairSync 和 noFairSync 就算是一個(gè)完整的 AQS 了。此時(shí)看一下加解鎖的流程冀痕。
只說(shuō)公平鎖荔睹,因?yàn)榉枪芥i就只是少了一個(gè)判斷。
首先 sync 調(diào)用 lock 方法言蛇,讓后 lock 調(diào)用了 AQS 的 acquire(1) 也就是獲取一個(gè)鎖資源僻他。
acquire 就先調(diào)用 tryAcquire(1) 嘗試獲取鎖,這時(shí)候代碼又回調(diào)到 sync 中的實(shí)現(xiàn)的 tryAcquire 方法腊尚,這個(gè)方法先判斷鎖是否已經(jīng)被別的線程使用吨拗,然后需要確定沒(méi)有更早的線程在同步隊(duì)列等待獲取鎖,才把當(dāng)前線程設(shè)置為獨(dú)占線程婿斥,并設(shè)置 state 值獲取鎖劝篷。但是如果有代碼在臨界區(qū)需要判斷是否為當(dāng)前線程,因?yàn)殒i是可重入的受扳。如果是當(dāng)前線程則 state 加上請(qǐng)求鎖的個(gè)數(shù)携龟,返回。
這時(shí)候又回到 AQS 中勘高,如果上面嘗試獲取鎖的過(guò)程失敗峡蟋,就需要調(diào)用 addWaiter 將當(dāng)前線程封裝成一個(gè)獨(dú)占節(jié)點(diǎn)坟桅,等待狀態(tài)默認(rèn)為 0侣诵,并且返回當(dāng)前節(jié)點(diǎn)廷雅。
加入同步隊(duì)列后棚瘟,再調(diào)用 acquireQueued 方法稠鼻,當(dāng)此線程是同步隊(duì)列中等待的第一個(gè)線程則自旋嘗試獲取鎖,畢竟很可能正在執(zhí)行的線程馬上就會(huì)釋放鎖了蚯斯,再進(jìn)行休眠不合適箫攀。如果自旋獲取鎖失敗則判斷節(jié)點(diǎn)狀態(tài)是否為 SIGNAL 然后執(zhí)行等待操作抑月。
鎖獲取成功則把當(dāng)前節(jié)點(diǎn)設(shè)置為頭結(jié)點(diǎn)子漩,把 thread = null
至此豫喧,Acquire 方法執(zhí)行結(jié)束。
然后調(diào)用 unlock 方法解鎖操作幢泼。
解鎖操作就沒(méi)那么麻煩紧显,首先還是調(diào)用到了 AQS 中的 release 方法,這個(gè)方法首先嘗試解鎖當(dāng)前線程缕棵,又回調(diào)到了 sync 中的 tryRelease 孵班。
tryRelease 邏輯比較簡(jiǎn)單,使用 AQS 中的 state 減去釋放的資源數(shù)招驴,等于 0 代表完全釋放篙程,否則釋放失敗。
如果 tryRelease 成功執(zhí)行就要去喚醒同步隊(duì)列中的后繼節(jié)點(diǎn)别厘,繼續(xù)執(zhí)行虱饿。
至此,release 方法執(zhí)行完畢触趴。
4. AQS 中的要方法
1. get/setState
??這兩個(gè)方法主要是對(duì) state 變量的 volatile 的讀寫(xiě)郭厌,其實(shí)里面就就是普通的 get/set 方法。但是注意的一點(diǎn)就是 state 是 volatile 的雕蔽。
// 對(duì)狀態(tài)變量的 volatile 讀寫(xiě)
protected final int getState() {
return state;
}
protected final void setState(int newState) {
state = newState;
}
2. release/tryRelease/unparkSuccessor/fullyRelease
?? 這幾個(gè)方法在一起說(shuō)主要是因?yàn)樗麄冎g存在調(diào)用鏈,首先來(lái)看 release 這個(gè)方法我們?cè)谏厦嬉卜治隽吮瞿龋锩嬲{(diào)用了 tryRelease 批狐、unparkSuccessor。 也就是首先調(diào)用 tryRelease 來(lái)釋放當(dāng)前線程的鎖前塔,如果釋放成功就調(diào)用 unparkSuccessor 來(lái)喚醒同步隊(duì)列中后繼節(jié)點(diǎn)嚣艇。其中 tryRelease 是由子類(lèi)來(lái)實(shí)現(xiàn),里面的主要邏輯就是看當(dāng)前的 state 變量的值在修改過(guò)后是否為0 华弓。這里還有一個(gè) fullRelease 主要是在 ConditionObject 中調(diào)用的食零,當(dāng)執(zhí)行 await 的操作的時(shí)會(huì)執(zhí)行此方法釋放鎖。
// 嘗試釋放鎖
public final boolean release(int arg) {
// 如果釋放鎖成功 喚醒同步隊(duì)列中的后繼節(jié)點(diǎn)
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
// 喚醒同步隊(duì)列中的后繼節(jié)點(diǎn)
private void unparkSuccessor(Node node) {
// node 一般就是當(dāng)前正在運(yùn)行的線程
int ws = node.waitStatus;
// 當(dāng)前線程置為初始狀態(tài) 可以失敗
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
// 找到同步隊(duì)列中的下一個(gè)節(jié)點(diǎn)
Node s = node.next;
if (s == null || s.waitStatus > 0) { //沒(méi)有下一個(gè)節(jié)點(diǎn)或者被取消
s = null;
// 從后往前找第一個(gè)沒(méi)有被取消的線程
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
// 喚醒那個(gè)線程
if (s != null)
LockSupport.unpark(s.thread);
}
final int fullyRelease(Node node) {
boolean failed = true;
try {
int savedState = getState();
if (release(savedState)) {
failed = false;
return savedState;
} else {
throw new IllegalMonitorStateException();
}
} finally {
if (failed)
node.waitStatus = Node.CANCELLED;
}
}
3. acquire/tryAcquire/addWaiter/acquireQueued
這個(gè)和上面的一樣寂屏,在執(zhí)行了 acquire 后贰谣,會(huì)去調(diào)用子類(lèi)復(fù)寫(xiě)的 tryAcquire 方法娜搂,這個(gè)方法就是看有否有代碼塊在臨界區(qū),沒(méi)有的話直接獲取鎖(非公平鎖)吱抚,設(shè)置 state百宇,有的話要判斷是不是當(dāng)前線程能否進(jìn)行重入操作,否則就獲取失敗秘豹。失敗后會(huì)調(diào)用 addWaiter 携御,new 一個(gè)新的節(jié)點(diǎn)加入到同步隊(duì)列,接著調(diào)用了 acquireQueued 如果這個(gè)節(jié)點(diǎn)是同步隊(duì)列中的第一個(gè)等待的線程(但不是第一個(gè)節(jié)點(diǎn)既绕,因?yàn)榈谝粋€(gè)節(jié)點(diǎn)是 thread=null 的運(yùn)行中的線程)就自旋一段時(shí)間看能否獲取到鎖啄刹。不能則 park 等待。
// 獲取鎖
public final void acquire(int arg) {
// 嘗試獲取鎖 失敗則加入同步隊(duì)列 如果是同步隊(duì)列中的第一個(gè)線程就自旋獲取鎖
// 上面的步驟的自旋獲取鎖階段凄贩,返回的是是否需要中斷誓军,所以下面就進(jìn)行 selfInterrupt
// tryAcquire 是模板方法,因?yàn)閷?duì)于公平鎖和非公平鎖獲取鎖方式不同
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
// 創(chuàng)建一個(gè)節(jié)點(diǎn)放入到同步對(duì)列中 可傳入是否為獨(dú)占鎖 返回當(dāng)前節(jié)點(diǎn)
private Node addWaiter(Node mode) {
// 默認(rèn)的 status 是 0
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;
// 把 tail 設(shè)置為 node 成功說(shuō)明沒(méi)有競(jìng)爭(zhēng)
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
// 失敗則就說(shuō)明空隊(duì)列 創(chuàng)建頭結(jié)點(diǎn)
enq(node);
return node;
}
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
// 自旋獲取鎖
for (;;) {
// 獲取前驅(qū)節(jié)點(diǎn)
final Node p = node.predecessor();
// 如果前驅(qū)是空的頭結(jié)點(diǎn)怎炊,那么也就是說(shuō)當(dāng)前線程就是隊(duì)列中的第一個(gè)線程 并嘗試獲取鎖 成功的話方法返回中斷情況
if (p == head && tryAcquire(arg)) {
// 把當(dāng)前節(jié)點(diǎn)設(shè)置為頭結(jié)點(diǎn) thread=null 也就可以看做當(dāng)前線程在運(yùn)行谭企,所以就不在同步隊(duì)列
setHead(node);
// gc
p.next = null; // help GC
failed = false;
return interrupted;
}
// 如果獲取鎖失敗,檢測(cè)為 SIGNAL 或者設(shè)置為 SIGNAL 然后讓此線程等待 等待操作在 parkAndCheckInterrupt 中完成
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
interrupted = true;
}
} finally {
// 失敗 取消
if (failed)
cancelAcquire(node);
}
}
5. 總結(jié)
?? 其實(shí)到這里 ReentrantLock 已經(jīng)講完了评肆,因?yàn)樗讓尤空{(diào)用的是 Sync 中的方法债查,也就是全都是調(diào)用了 AQS 中的方法。而 AQS 中的大部分重要的方法都已經(jīng)看過(guò)了瓜挽。