摘自一行一行源碼分析清楚AbstractQueuedSynchronizer
// 下面這個方法,參數(shù)node,經(jīng)過addWaiter(Node.EXCLUSIVE),此時已經(jīng)進入阻塞隊列
// 注意一下:如果acquireQueued(addWaiter(Node.EXCLUSIVE), arg))返回true的話,
// 意味著上面這段代碼將進入selfInterrupt(),所以正常情況下,下面應(yīng)該返回false
// 這個方法非常重要,應(yīng)該說真正的線程掛起,然后被喚醒后去獲取鎖,都在這個方法里了
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
// p == head 說明當前節(jié)點雖然進到了阻塞隊列,但是是阻塞隊列的第一個,因為它的前驅(qū)是head
// 注意,阻塞隊列不包含head節(jié)點,head一般指的是占有鎖的線程,head后面的才稱為阻塞隊列
// 所以當前節(jié)點可以去試搶一下鎖
// 這里我們說一下,為什么可以去試試:
// 首先,它是隊頭,這個是第一個條件,其次,當前的head有可能是剛剛初始化的node,
// enq(node) 方法里面有提到,head是延時初始化的,而且new Node()的時候沒有設(shè)置任何線程
// 也就是說,當前的head不屬于任何一個線程,所以作為隊頭,可以去試一試,
// tryAcquire已經(jīng)分析過了, 忘記了請往前看一下,就是簡單用CAS試操作一下state
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
// 到這里,說明上面的if分支沒有成功,要么當前node本來就不是隊頭,
// 要么就是tryAcquire(arg)沒有搶贏別人,繼續(xù)往下看
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
// 什么時候 failed 會為 true???
// tryAcquire() 方法拋異常的情況
if (failed)
cancelAcquire(node);
}
}
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
// 前驅(qū)節(jié)點 waitStatus大于0 ,之前說過,大于0 說明前驅(qū)節(jié)點取消了排隊。
// 這里需要知道這點:進入阻塞隊列排隊的線程會被掛起,而喚醒的操作是由前驅(qū)節(jié)點完成的。
// 所以下面這塊代碼說的是將當前節(jié)點的prev指向waitStatus<=0的節(jié)點,
// 簡單說,就是為了找個好爹,因為你還得依賴它來喚醒呢,如果前驅(qū)節(jié)點取消了排隊,
// 找前驅(qū)節(jié)點的前驅(qū)節(jié)點做爹,往前遍歷總能找到一個好爹的
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
// 仔細想想,如果進入到這個分支意味著什么
// 前驅(qū)節(jié)點的waitStatus不等于-1和1,那也就是只可能是0,-2,-3
// 在我們前面的源碼中,都沒有看到有設(shè)置waitStatus的,所以每個新的node入隊時,waitStatu都是0
// 正常情況下,前驅(qū)節(jié)點是之前的 tail,那么它的 waitStatus 應(yīng)該是 0
// 用CAS將前驅(qū)節(jié)點的waitStatus設(shè)置為Node.SIGNAL(也就是-1)
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
int ws = node.waitStatus;
// 如果head節(jié)點當前waitStatus<0, 將其修改為0
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
// 下面的代碼就是喚醒后繼節(jié)點,但是有可能后繼節(jié)點取消了等待(waitStatus==1)
// 從隊尾往前找,找到waitStatus<=0的所有節(jié)點中排在最前面的
// 不一定是head的后繼節(jié)點, 在shouldParkAfterFailedAcquire會把waitStatus>0的去掉
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
// 從后往前找,仔細看代碼,不必擔心中間有節(jié)點取消(waitStatus==1)的情況
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
// 喚醒線程
LockSupport.unpark(s.thread);
}
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this); // 線程會被被掛起在這里
return Thread.interrupted();
}
公平鎖的總結(jié):
鎖狀態(tài)。我們要知道鎖是不是被別的線程占有了,這個就是 state 的作用,它為 0 的時候代表沒有線程占有鎖,可以去爭搶這個鎖,用 CAS 將 state 設(shè)為 1,如果 CAS 成功,說明搶到了鎖,這樣其他線程就搶不到了,如果鎖重入的話,state進行 +1 就可以,解鎖就是減 1,直到 state 又變?yōu)?0,代表釋放鎖,所以 lock() 和 unlock() 必須要配對啊。然后喚醒等待隊列中的第一個線程,讓其來占有鎖。
線程的阻塞和解除阻塞。AQS 中采用了 LockSupport.park(thread) 來掛起線程,用 unpark 來喚醒線程。
阻塞隊列。因為爭搶鎖的線程可能很多,但是只能有一個線程拿到鎖,其他的線程都必須等待,這個時候就需要一個 queue 來管理這些線程,AQS 用的是一個 FIFO 的隊列,就是一個鏈表,每個 node 都持有后繼節(jié)點的引用。
這里可以簡單說下 waitStatus 中 SIGNAL(-1) 狀態(tài)的意思,Doug Lea 注釋的是:代表后繼節(jié)點需要被喚醒。也就是說這個 waitStatus 其實代表的不是自己的狀態(tài),而是后繼節(jié)點的狀態(tài),我們知道,每個 node 在入隊的時候,都會把前驅(qū)節(jié)點的狀態(tài)改為 SIGNAL,然后阻塞,等待被前驅(qū)喚醒。這里涉及的是兩個問題:有線程取消了排隊、喚醒操作。