哈嘍大家好,我是阿Q。
最近是上班忙項目,下班帶娃,忙的不可開交,連摸魚的時間都沒有了。今天趁假期用圖解的方式從源碼角度給大家說一下ReentrantLock加鎖解鎖的全過程。系好安全帶,發(fā)車了。
簡單使用
在聊它的源碼之前,我們先來做個簡單的使用說明。當(dāng)我在IDEA中創(chuàng)建了一個簡單的Demo之后,它會給出以下提示

提示文字
在使用阻塞等待獲取鎖的方式中,必須在try代碼塊之外,并且在加鎖方法與try代碼塊之間沒有任何可能拋出異常的方法調(diào)用,避免加鎖成功后,在finally中無法解鎖。
1、如果在lock方法與try代碼塊之間的方法調(diào)用拋出異常,那么無法解鎖,造成其它線程無法成功獲取鎖。
2、如果lock方法在try代碼塊之內(nèi),可能由于其它方法拋出異常,導(dǎo)致在finally代碼塊中,unlock對未加鎖的對象解鎖,它會調(diào)用AQS的tryRelease方法(取決于具體實現(xiàn)類),拋出IllegalMonitorStateException異常。
3、在Lock對象的lock方法實現(xiàn)中可能拋出unchecked異常,產(chǎn)生的后果與說明二相同。
java.concurrent.LockShouldWithTryFinallyRule.rule.desc
還舉了兩個例子,正確案例如下:
Lock lock = new XxxLock();
// ...
lock.lock();
try {
doSomething();
doOthers();
} finally {
lock.unlock();
}
錯誤案例如下:
Lock lock = new XxxLock();
// ...
try {
// 如果在此拋出異常,會直接執(zhí)行 finally 塊的代碼
doSomething();
// 不管鎖是否成功,finally 塊都會執(zhí)行
lock.lock();
doOthers();
} finally {
lock.unlock();
}
AQS
上邊的案例中加鎖調(diào)用的是lock()方法,解鎖用的是unlock()方法,而通過查看源碼發(fā)現(xiàn)它們都是調(diào)用的內(nèi)部靜態(tài)抽象類Sync的相關(guān)方法。
abstract static class Sync extends AbstractQueuedSynchronizer
Sync 是通過繼承AbstractQueuedSynchronizer來實現(xiàn)的,沒錯,AbstractQueuedSynchronizer就是AQS的全稱。AQS內(nèi)部維護(hù)著一個FIFO的雙向隊列(CLH),ReentrantLock也是基于它來實現(xiàn)的,先來張圖感受下。

Node 屬性
//此處是 Node 的部分屬性
static final class Node {
//排他鎖標(biāo)識
static final Node EXCLUSIVE = null;
//如果帶有這個標(biāo)識,證明是失效了
static final int CANCELLED = 1;
//具有這個標(biāo)識,說明后繼節(jié)點需要被喚醒
static final int SIGNAL = -1;
//Node對象存儲標(biāo)識的地方
volatile int waitStatus;
//指向上一個節(jié)點
volatile Node prev;
//指向下一個節(jié)點
volatile Node next;
//當(dāng)前Node綁定的線程
volatile Thread thread;
//返回前驅(qū)節(jié)點即上一個節(jié)點,如果前驅(qū)節(jié)點為空,拋出異常
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
}
對于里邊的waitStatus屬性,我們需要做個解釋:(非常重要)
- CANCELLED(1):當(dāng)前節(jié)點取消獲取鎖。當(dāng)?shù)却瑫r或被中斷(響應(yīng)中斷),會觸發(fā)變更為此狀態(tài),進(jìn)入該狀態(tài)后節(jié)點狀態(tài)不再變化;
- SIGNAL(-1):后面節(jié)點等待當(dāng)前節(jié)點喚醒;
- CONDITION(-2):Condition?中使用,當(dāng)前線程阻塞在Condition?,如果其他線程調(diào)用了Condition的signal方法,這個結(jié)點將從等待隊列轉(zhuǎn)移到同步隊列隊尾,等待獲取同步鎖;
- PROPAGATE(-3):共享模式,前置節(jié)點喚醒后面節(jié)點后,喚醒操作無條件傳播下去;
- 0:中間狀態(tài),當(dāng)前節(jié)點后面的節(jié)點已經(jīng)喚醒,但是當(dāng)前節(jié)點線程還沒有執(zhí)行完成;
AQS 屬性
// 頭結(jié)點
private transient volatile Node head;
// 尾結(jié)點
private transient volatile Node tail;
//0->1 拿到鎖,大于0 說明當(dāng)前已經(jīng)有線程占用了鎖資源
private volatile int state;
今天我們先簡單了解下AQS的構(gòu)造以幫助大家更好的理解ReentrantLock,至于深層次的東西先不做展開!
加鎖
對AQS的結(jié)構(gòu)有了基本了解之后,我們正式進(jìn)入主題——加鎖。從源碼中可以看出鎖被分為公平鎖和非公平鎖。

/**
* 公平鎖代碼
*/
final void lock(){
acquire(1);
}
/**
* 非公平鎖代碼
*/
final void lock(){
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
初步查看代碼發(fā)現(xiàn)非公平鎖似乎包含公平鎖的邏輯,所以我們就從“非公平鎖”開始。
非公平鎖
final void lock(){
//通過 CAS 的方式嘗試將 state 從0改為1,
//如果返回 true,代表修改成功,獲得鎖資源;
//如果返回false,代表修改失敗,未獲取鎖資源
if (compareAndSetState(0, 1))
// 將屬性exclusiveOwnerThread設(shè)置為當(dāng)前線程,該屬性是AQS的父類提供的
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
compareAndSetState():底層調(diào)用的是unsafe的compareAndSwapInt,該方法是原子操作;
假設(shè)有兩個線程(t1、t2)在競爭鎖資源,線程1獲取鎖資源之后,執(zhí)行setExclusiveOwnerThread操作,設(shè)置屬性值為當(dāng)前線程t1

此時,當(dāng)t2想要獲取鎖資源,調(diào)用lock()方法之后,執(zhí)行compareAndSetState(0, 1)返回false,會走else執(zhí)行acquire()方法。
方法查看
public final void accquire(int arg){
// tryAcquire 再次嘗試獲取鎖資源,如果嘗試成功,返回true,嘗試失敗返回false
if (!tryAcquire(arg) &&
// 走到這,代表獲取鎖資源失敗,需要將當(dāng)前線程封裝成一個Node,追加到AQS的隊列中
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
// 線程中斷
selfInterrupt();
}
accquire()中涉及的方法比較多,我們將進(jìn)行拆解,一個一個來分析,順序:tryAcquire() -> addWaiter() -> acquireQueued()
查看 tryAcquire() 方法
//AQS中
protected boolean tryAcquire(int arg){
//AQS 是基類,具體實現(xiàn)在自己的類中實現(xiàn),我們?nèi)ゲ榭础胺枪芥i”中的實現(xiàn)
throw new UnsupportedOperationException();
}
//ReentrantLock 中
protected final boolean tryAcquire(int acquires){
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires){
// 獲取當(dāng)前線程
final Thread current = Thread.currentThread();
//獲取AQS 的 state
int c = getState();
// 如果 state 為0,代表嘗試再次獲取鎖資源
if (c == 0) {
// 步驟同上:通過 CAS 的方式嘗試將 state 從0改為1,
//如果返回 true,代表修改成功,獲得鎖資源;
//如果返回false,代表修改失敗,未獲取鎖資源
if (compareAndSetState(0, acquires)) {
//設(shè)置屬性為當(dāng)前線程
setExclusiveOwnerThread(current);
return true;
}
}
//當(dāng)前占有鎖資源的線程是否是當(dāng)前線程,如果是則證明是可重入操作
else if (current == getExclusiveOwnerThread()) {
//將 state + 1
int nextc = c + acquires;
//為什么會小于 0 呢?因為最大值 + 1 后會將符號位的0改為1 會變成負(fù)數(shù)(可參考Integer.MAX_VALUE + 1)
if (nextc < 0) // overflow
//加1后小于0,超出鎖可重入的最大值,拋異常
throw new Error("Maximum lock count exceeded");
//設(shè)置 state 狀態(tài)
setState(nextc);
return true;
}
return false;
}
因為線程1已經(jīng)獲取到了鎖,此時state為1,所以不走nonfairTryAcquire()的if。又因為當(dāng)前是線程2,不是占有當(dāng)前鎖的線程1,所以也不會走else if,即tryAcquire()方法返回false。
查看 addWaiter() 方法
走到本方法中,代表獲取鎖資源失敗。addWaiter()將沒有獲取到鎖資源的線程甩到隊列的尾部。
private Node addWaiter(Node mode){
//創(chuàng)建 Node 類,并且設(shè)置 thread 為當(dāng)前線程,設(shè)置為排它鎖
Node node = new Node(Thread.currentThread(), mode);
// 獲取 AQS 中隊列的尾部節(jié)點
Node pred = tail;
// 如果 tail == null,說明是空隊列,
// 不為 null,說明現(xiàn)在隊列中有數(shù)據(jù),
if (pred != null) {
// 將當(dāng)前節(jié)點的 prev 指向剛才的尾部節(jié)點,那么當(dāng)前節(jié)點應(yīng)該設(shè)置為尾部節(jié)點
node.prev = pred;
// CAS 將 tail 節(jié)點設(shè)置為當(dāng)前節(jié)點
if (compareAndSetTail(pred, node)) {
// 將之前尾節(jié)點的 next 設(shè)置為當(dāng)前節(jié)點
pred.next = node;
// 返回當(dāng)前節(jié)點
return node;
}
}
enq(node);
return node;
}
當(dāng)tail不為空,即隊列中有數(shù)據(jù)時,我們來圖解一下pred!=null代碼塊中的代碼。初始化狀態(tài)如下,pred指向尾節(jié)點,node指向新的節(jié)點。

node.prev = pred;將node的前驅(qū)節(jié)點設(shè)置為pred指向的節(jié)點

compareAndSetTail(pred, node)通過CAS的方式嘗試將當(dāng)前節(jié)點node設(shè)置為尾結(jié)點,此處我們假設(shè)設(shè)置成功,則FIFO隊列的tail指向node節(jié)點。

pred.next = node;將pred節(jié)點的后繼節(jié)點設(shè)置為node節(jié)點,此時node節(jié)點成功進(jìn)入FIFO隊列尾部。

而當(dāng)pred為空,即隊列中沒有節(jié)點或?qū)ode節(jié)點設(shè)置為尾結(jié)點失敗時,會走enq()方法。我們列舉的例子就符合pred為空的情況,就讓我們以例子為基礎(chǔ)繼續(xù)分析吧。
//現(xiàn)在沒人排隊,我是第一個 || 前邊CAS失敗也會進(jìn)入這個位置重新往隊列尾巴去塞
private Node enq(final Node node){
//死循環(huán)
for (;;) {
//重新獲取tail節(jié)點
Node t = tail;
// 沒人排隊,隊列為空
if (t == null) {
// 初始化一個 Node 為 head,而這個head 沒有意義
if (compareAndSetHead(new Node()))
// 將頭尾都指向了這個初始化的Node,第一次循環(huán)結(jié)束
tail = head;
} else {
// 有人排隊,往隊列尾巴塞
node.prev = t;
// CAS 將 tail 節(jié)點設(shè)置為當(dāng)前節(jié)點
if (compareAndSetTail(t, node)) {
//將之前尾節(jié)點的 next 設(shè)置為當(dāng)前節(jié)點
t.next = node;
return t;
}
}
}
}
進(jìn)入死循環(huán),首先會走if方法的邏輯,通過CAS的方式嘗試將一個新節(jié)點設(shè)置為head節(jié)點,然后將tail也指向新節(jié)點??梢钥闯鲫犃兄械念^節(jié)點只是個初始化的節(jié)點,沒有任何意義。

繼續(xù)走死循環(huán)中的代碼,此時t不為null,所以會走else方法。將node的前驅(qū)節(jié)點指向t,通過CAS方式將當(dāng)前節(jié)點node設(shè)置為尾結(jié)點,然后將t的后繼節(jié)點指向node。此時線程2的節(jié)點就被成功塞入FIFO隊列尾部。

查看 acquireQueued()方法
將已經(jīng)在隊列中的node嘗試去獲取鎖否則掛起。
final boolean acquireQueued(final Node node, int arg){
// 獲取鎖資源的標(biāo)識,失敗為 true,成功為 false
boolean failed = true;
try {
// 線程中斷的標(biāo)識,中斷為 true,不中斷為 false
boolean interrupted = false;
for (;;) {
// 獲取當(dāng)前節(jié)點的上一個節(jié)點
final Node p = node.predecessor();
//p為頭節(jié)點,嘗試獲取鎖操作
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null;
// 將獲取鎖失敗標(biāo)識置為false
failed = false;
// 獲取到鎖資源,不會被中斷
return interrupted;
}
// p 不是 head 或者 沒拿到鎖資源,
if (shouldParkAfterFailedAcquire(p, node) &&
// 基于 Unsafe 的 park方法,掛起線程
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
這里又出現(xiàn)了一次死循環(huán),首先獲取當(dāng)前節(jié)點的前驅(qū)節(jié)點p,如果p是頭節(jié)點(頭節(jié)點沒有意義),說明node是head后的第一個節(jié)點,此時當(dāng)前獲取鎖資源的線程1可能會釋放鎖,所以線程2可以再次嘗試獲取鎖。
假設(shè)獲取成功,證明拿到鎖資源了,將node節(jié)點設(shè)置為head節(jié)點,并將node節(jié)點的pre和thread設(shè)置為null。因為拿到鎖資源了,node節(jié)點就不需要排隊了。
將頭節(jié)點p的next置為null,此時p節(jié)點就不在隊列中存在了,可以幫助GC回收(可達(dá)性分析)。failed設(shè)置為false,表明獲取鎖成功;interrupted為false,則線程不會中斷。

如果p不是head節(jié)點或者沒有拿到鎖資源,會執(zhí)行下邊的代碼,因為我們的線程1沒有釋放鎖資源,所以線程2獲取鎖失敗,會繼續(xù)往下執(zhí)行。
//該方法的作用是保證上一個節(jié)點的waitStatus狀態(tài)為-1(為了喚醒后繼節(jié)點)
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node){
//獲取上一個節(jié)點的狀態(tài),該狀態(tài)為-1,才會喚醒下一個節(jié)點。
int ws = pred.waitStatus;
// 如果上一個節(jié)點的狀態(tài)是SIGNAL即-1,可以喚醒下一個節(jié)點,直接返回true
if (ws == Node.SIGNAL)
return true;
// 如果上一個節(jié)點的狀態(tài)大于0,說明已經(jīng)失效了
if (ws > 0) {
do {
// 將node 的節(jié)點與 pred 的前一個節(jié)點相關(guān)聯(lián),并將前一個節(jié)點賦值給 pred
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0); // 一直找到小于等于0的
// 將重新標(biāo)識好的最近的有效節(jié)點的 next 指向當(dāng)前節(jié)點
pred.next = node;
} else {
// 小于等于0,但是不等于-1,將上一個有效節(jié)點狀態(tài)修改為-1
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
只有節(jié)點的狀態(tài)為-1,才會喚醒后一個節(jié)點,如果節(jié)點狀態(tài)未設(shè)置,默認(rèn)為0。
圖解一下ws>0的過程,因為ws>0的節(jié)點為失效節(jié)點,所以do...while中會重復(fù)向前查找前驅(qū)節(jié)點,直到找到第一個ws<=0的節(jié)點為止,將node節(jié)點掛到該節(jié)點上。

我們的pred是頭結(jié)點且未設(shè)置狀態(tài),所以狀態(tài)為0,會走else。通過CAS嘗試將pred節(jié)點的waitStatus設(shè)置為-1,表明node節(jié)點需要被pred喚醒。

shouldParkAfterFailedAcquire()返回false,繼續(xù)執(zhí)行acquireQueued()中的死循環(huán)。
步驟和上邊一樣,node的前驅(qū)節(jié)點還是head,繼續(xù)嘗試獲取鎖。如果線程1釋放了鎖,線程2就可以拿到,返回true;否則繼續(xù)調(diào)用shouldParkAfterFailedAcquire(),因為上一步已經(jīng)將前驅(qū)結(jié)點的ws設(shè)置為-1了,所以直接返回true。
執(zhí)行parkAndCheckInterrupt()方法,通過UNSAFE.park();方法阻塞當(dāng)前線程2。等以后執(zhí)行unpark方法的時候,如果node是頭節(jié)點后的第一個節(jié)點,會進(jìn)入acquireQueued()方法中走if (p == head && tryAcquire(arg))的邏輯獲取鎖資源并結(jié)束死循環(huán)。
查看cancelAcquire()方法
該方法執(zhí)行的機率約等于0,為什么這么說呢?因為針對failed屬性,只有JVM內(nèi)部出現(xiàn)問題時,才可能出現(xiàn)異常,執(zhí)行該方法。
// node 為當(dāng)前節(jié)點
private void cancelAcquire(Node node){
if (node == null)
return;
node.thread = null;
// 上一個節(jié)點
Node pred = node.prev;
// 節(jié)點狀態(tài)大于0,說明節(jié)點失效
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
// 將第一個不是失效節(jié)點的后繼節(jié)點聲明出來
Node predNext = pred.next;
// 節(jié)點狀態(tài)變?yōu)槭?br> node.waitStatus = Node.CANCELLED;
// node為尾節(jié)點,cas設(shè)置pred為尾節(jié)點
if (node == tail && compareAndSetTail(node, pred)) {
//cas將pred的next設(shè)置為null
compareAndSetNext(pred, predNext, null);
} else {
int ws;
// 中間節(jié)點
// 如果上一個節(jié)點不是head 節(jié)點
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
// 前邊已經(jīng)判斷了大于0的操作,
// pred 是需要喚醒后繼節(jié)點的,所以當(dāng) waitStatus 不為 -1 時,需要將 pred 節(jié)點的 waitStatus 設(shè)置為 -1
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
Node next = node.next;
if (next != null && next.waitStatus <= 0)
// CAS 嘗試將 pred 的 next 指向當(dāng)前節(jié)點的 next
compareAndSetNext(pred, predNext, next);
} else {
// head 節(jié)點,喚醒后繼節(jié)點
unparkSuccessor(node);
}
node.next = node; // help GC
}
}
執(zhí)行到while時找到前驅(qū)節(jié)點中最近的有效節(jié)點,把當(dāng)前節(jié)點node掛到有效節(jié)點后邊,可以過濾掉當(dāng)前節(jié)點前的失效節(jié)點。聲明出有效節(jié)點的第一個后繼無效節(jié)點predNext,并把當(dāng)前的node節(jié)點狀態(tài)設(shè)置為失效狀態(tài)。

if中的操作:如果當(dāng)前節(jié)點是尾節(jié)點,CAS嘗試將最近的有效節(jié)點設(shè)置為尾節(jié)點,并將尾節(jié)點的next設(shè)置為null。

else中的操作:
如果pred節(jié)點不是頭結(jié)點即中間節(jié)點,并且pred的waitStatus為-1或者waitStatus<=0,為了讓pred節(jié)點能喚醒后繼節(jié)點,需要設(shè)置為-1,并且pred節(jié)點的線程不為空。獲取node節(jié)點的后繼節(jié)點,如果后繼節(jié)點有效,CAS嘗試將pred的next指向node節(jié)點的next。

當(dāng)其他節(jié)點來找有效節(jié)點的時候走當(dāng)前node的prev這條線,而不是再一個一個往前找,可以提高效率。
如果是頭結(jié)點則喚醒后繼節(jié)點。
最后將node節(jié)點的next指向自己。
解鎖
釋放鎖是不區(qū)分公平鎖和非公平鎖的,釋放鎖的核心是將state由大于 0 的數(shù)置為 0。廢話不多說,直接上代碼
//釋放鎖方法
public void unlock(){
sync.release(1);
}
public final boolean release(int arg){
//嘗試釋放鎖資源,如果釋放成功,返回true
if (tryRelease(arg)) {
Node h = head;
// head 不為空且 head 的 ws 不為0(如果為0,代表后邊沒有其他線程掛起)
if (h != null && h.waitStatus != 0)
// AQS的隊列中有 node 在排隊,并且線程已經(jīng)掛起
// 需要喚醒被掛起的 Node
unparkSuccessor(h);
return true;
}
// 代表釋放一次沒有完全釋放
return false;
}
如果釋放鎖成功,需要獲取head節(jié)點。如果頭結(jié)點不為空且waitStatus不為0,則證明有node在排隊,執(zhí)行喚醒掛起其他node的操作。
查看tryRelease()方法
protected final boolean tryRelease(int releases){
//獲取當(dāng)前鎖的狀態(tài),先進(jìn)行減1操作,代表釋放一次鎖資源 int c = getState() - releases;
//如果釋放鎖的線程不是占用鎖的線程,直接拋出異常 if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
// 如果 c 為0 ,代表鎖完全釋放了,如果不為0,代表鎖之前重入了,一次沒釋放掉,等待下次再次執(zhí)行時,再次判斷 if (c == 0) {
// 釋放鎖標(biāo)志為 true,代表完全釋放了 free = true;
// 將占用互斥鎖的標(biāo)識置為 null setExclusiveOwnerThread(null);
} // 設(shè)置 state 狀態(tài) setState(c);
return free;
}
我們的例子中線程1占用鎖資源,線程1釋放鎖之后,state為0。進(jìn)入if操作,將釋放標(biāo)志更新為true,將FIFO隊列的exclusiveOwnerThread標(biāo)志置為null。

查看unparkSuccessor()方法
用于喚醒AQS中被掛起的線程。
// 注意當(dāng)前的 node 節(jié)點是 head 節(jié)點
private void unparkSuccessor(Node node){
//獲取 head 的狀態(tài)
int ws = node.waitStatus;
if (ws < 0)
// CAS 將 node 的 ws 設(shè)置為0,代表當(dāng)前 node 接下來會舍棄
compareAndSetWaitStatus(node, ws, 0);
// 獲取頭節(jié)點的下一個節(jié)點
Node s = node.next;
// 如果下一個節(jié)點為null 或者 下一個節(jié)點為失效節(jié)點,需要找到離 head 最近的有效node
if (s == null || s.waitStatus > 0) {
s = null;
// 從尾節(jié)點開始往前找不等于null且不是node的節(jié)點
for (Node t = tail; t != null && t != node; t = t.prev)
// 如果該節(jié)點有效,則將s節(jié)點指向t節(jié)點
if (t.waitStatus <= 0)
s = t;
}
// 找到最近的node后,直接喚醒
if (s != null)
LockSupport.unpark(s.thread);
}
問題解析:為什么要從尾結(jié)點往前查找呢?
因為在addWaiter方法中是先給prev指針賦值,最后才將上一個節(jié)點的next指針賦值,為了避免防止丟失節(jié)點或者跳過節(jié)點,必須從后往前找。
我們舉例中head節(jié)點的狀態(tài)為-1,通過CAS的方式將head節(jié)點的waitStatus設(shè)置為0。

我們的頭結(jié)點的后繼節(jié)點是線程2所在的節(jié)點,不為null,所以這邊會執(zhí)行unpark操作,從上邊的acquireQueued()內(nèi)的parkAndCheckInterrupt()方法繼續(xù)執(zhí)行。
private final boolean parkAndCheckInterrupt(){
LockSupport.park(this);
//返回目標(biāo)線程是否中斷的布爾值:中斷返回true,不中斷返回false,且返回后會重置中斷狀態(tài)為未中斷
return Thread.interrupted();
}
因為線程2未中斷,所以返回false。繼續(xù)執(zhí)行acquireQueued()中的死循環(huán)
for (;;) {
// 獲取當(dāng)前節(jié)點的上一個節(jié)點
final Node p = node.predecessor();
//p為頭節(jié)點,嘗試獲取鎖操作
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null;
// 將獲取鎖失敗標(biāo)識置為false
failed = false;
// 獲取到鎖資源,不會被中斷
return interrupted;
}
// p 不是 head 或者 沒拿到鎖資源,
if (shouldParkAfterFailedAcquire(p, node) &&
// 基于 Unsafe 的 park方法,掛起線程
parkAndCheckInterrupt())
interrupted = true;
}
此時p是頭節(jié)點,且能獲取鎖成功,將exclusiveOwnerThread設(shè)置為線程2,即線程2 獲取鎖資源。
將node節(jié)點設(shè)置為head節(jié)點,并將node節(jié)點的pre和thread設(shè)置為null。因為拿到鎖資源了,node節(jié)點就不需要排隊了。
將頭節(jié)點p的next置為null,此時p節(jié)點就不在隊列中存在了,可以幫助GC回收(可達(dá)性分析)。failed設(shè)置為false,表明獲取鎖成功;interrupted為false,則線程不會中斷。

為什么被喚醒的線程要調(diào)用Thread.interrupted()清除中斷標(biāo)記
從上邊的方法可以看出,當(dāng)parkAndCheckInterrupt()方法返回true時,即Thread.interrupted()方法返回了true,也就是該線程被中斷了。為了讓被喚醒的線程繼續(xù)執(zhí)行后續(xù)獲取鎖的操作,就需要讓中斷的線程像沒有被中斷過一樣繼續(xù)往下執(zhí)行,所以在返回中斷標(biāo)記的同時要清除中斷標(biāo)記,將其設(shè)置為false。
清除中斷標(biāo)記之后不代表該線程不需要中斷了,所以在parkAndCheckInterrupt()方法返回true時,要自己設(shè)置一個中斷標(biāo)志interrupted = true,為的就是當(dāng)獲取到鎖資源執(zhí)行完相關(guān)的操作之后進(jìn)行中斷補償,故而需要執(zhí)行selfInterrupt()方法中斷線程。
以上就是我們加鎖解鎖的圖解過程了。最后我們再來說一下公平鎖和非公平鎖的區(qū)別。
區(qū)別
前邊已經(jīng)說過了,似乎非公平鎖包含了公平鎖的全部操作。打開公平鎖的代碼,我們發(fā)現(xiàn)accquire()方法中只有該方法的實現(xiàn)有點區(qū)別。

hasQueuedPredecessors()返回false時才會嘗試獲取鎖資源。該方法代碼實現(xiàn)如下
public final boolean hasQueuedPredecessors(){
Node t = tail;
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
h==t時,隊列為空,表示沒人排隊,可以獲取鎖資源;
隊列不為空,頭結(jié)點有后繼節(jié)點不為空且s節(jié)點獲取鎖的線程是當(dāng)前線程也可以獲取鎖資源,代表鎖重入操作;
總結(jié)
以上就是我們的全部內(nèi)容了,我們在最后再做個總結(jié):
- 代碼使用要合乎規(guī)范,避免加鎖成功后,在finally中無法解鎖;
- 理解AQS的FIFO?隊列以及Node?的相關(guān)屬性,尤其注意waitStatus的狀態(tài);
- 利用圖加深對非公平鎖源碼的理解;