獨佔式
獨佔式獲取
如果tryAcquire(arg)獲取鎖成功,直接返回;
如果獲取失敗,調用acquireQueued(…),根據其結果,判斷是否調用線程中斷。
public final void acquire(int arg) {
/* 如果獲取獨佔鎖失敗,並且在等待隊列中獲取鎖成功 */
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
/* 線程中斷 */
selfInterrupt();
}
static void selfInterrupt() {
Thread.currentThread().interrupt();
}
tryAcquire(…)是需要自己實現的抽象方法。
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
接下來看下,addWaiter(…)方法。
將當前線程封裝成節點,在等待隊列的尾部插入。
private Node addWaiter(Node mode) {
/* 將當前線程包裝成一個節點,模式是獨佔式 */
Node node = new Node(Thread.currentThread(), mode);
// 獲取尾節點
Node pred = tail;
if (pred != null) {
node.prev = pred;
/* cas方式設置尾節點,因爲有可能有多個節點此刻想在隊列尾部插入,
使用cas保證同一時刻只有一個節點會插入成功 */
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
/* 之前有獲取尾節點的操作,但是,有可能尾節點不存在,這時候執行enq(...) */
enq(node);
return node;
}
然後看下上面說的enq(…)方法:
上文說到,enq(…)是尾節點不存在時調用的方法。
使用enq(…)構造一個新的等待隊列。
private Node enq(final Node node) {
/* 使用循環,保證構造新的等待隊列以及插入尾節點一定成功 */
for (;;) {
Node t = tail;
/* 此時,等待隊列仍舊未存在 */
if (t == null) {
/* 構造新的等待隊列,設置頭節點以及尾節點 */
if (compareAndSetHead(new Node()))
tail = head;
} else {
/* 此時,等待隊列已經存在,執行在尾部插入新節點 */
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
最後剩下acquireQueued(…)。
這個方法做的是在等待隊列中的節點,如何嘗試獲取獨佔鎖。
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
/* 獲取前驅節點 */
final Node p = node.predecessor();
/* 前驅節點是頭節點,並且獲取鎖成功 */
if (p == head && tryAcquire(arg)) {
/* 設置該節點爲頭節點,因爲只有頭節點有機會獲取鎖 */
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
/* 獲取鎖失敗,判斷是否要阻塞 && 阻塞 */
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
private void setHead(Node node) {
head = node;
node.thread = null;
node.prev = null;
}
現在看下shouldParkAfterFailedAcquire(…)方法:
判斷在獲取鎖失敗後,是否應當阻塞。
主要以是否成功設置節點狀態爲SIGNAL來判斷
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
return true;
/* 節點狀態爲取消 */
if (ws > 0) {
/* 跳過這個取消狀態的節點 */
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.
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
線程阻塞:
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
最後,是cancalAcquire(…)的分析:
private void cancelAcquire(Node node) {
/* 如果節點不存在,直接返回 */
if (node == null)
return;
node.thread = null;
/* 跳過被取消的節點 */
Node pred = node.prev;
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
Node predNext = pred.next;
/* 設置狀態爲取消狀態 */
node.waitStatus = Node.CANCELLED;
// 圖1
if (node == tail && compareAndSetTail(node, pred)) {
compareAndSetNext(pred, predNext, null);
} else {
int ws;
// 圖2
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
Node next = node.next;
if (next != null && next.waitStatus <= 0)
compareAndSetNext(pred, predNext, next);
// 圖3
} else {
unparkSuccessor(node);
}
node.next = node; // help GC
}
}
圖片1:
圖2:
圖3:
獨佔式釋放
喚醒並釋放後繼節點
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
/* 頭節點存在,並且還沒有釋放鎖 */
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
tryRelease(…)方法同樣是抽象方法:
protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}
然後看下unparkSuccessor(…)方法:
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
/* 設置爲初始狀態 */
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
/* 後繼節點爲空或者後繼節點是取消狀態 */
if (s == null || s.waitStatus > 0) {
s = null;
/* 重新選取可用的後繼節點 */
/* 從尾節點開始,選取其前驅節點 */
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
/* 可用的後繼節點 */
if (s != null)
/* 喚醒線程 */
LockSupport.unpark(s.thread);
}
獨佔式獲取 + 響應中斷
對於中斷,拋出異常,而已。
之前的只是設置狀態而已,沒有對中斷作出實質性處理。
public final void acquireInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (!tryAcquire(arg))
doAcquireInterruptibly(arg);
}
private void doAcquireInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
/* 拋出異常 */
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
獨佔式獲取 + 響應中斷 + 等待超時
節點在等待隊列中等待過程中,增加超時
public final boolean tryAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
return tryAcquire(arg) ||
doAcquireNanos(arg, nanosTimeout);
}
private boolean doAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (nanosTimeout <= 0L)
return false;
/* 從節點加入到等待隊列中,開始計算截止時間 */
final long deadline = System.nanoTime() + nanosTimeout;
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return true;
}
/* 如果前驅節點是頭節點,嘗試獲取鎖。。。然後計算超時時間剩餘 */
/* 如果不是頭節點,直接計算剩餘時間 */
/* 無論是上述哪種,節點都已經加入到等待隊列中了 */
nanosTimeout = deadline - System.nanoTime();
if (nanosTimeout <= 0L)
return false;
if (shouldParkAfterFailedAcquire(p, node) &&
nanosTimeout > spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout);
if (Thread.interrupted())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
共享式
同一時刻,可以有多個線程獲取到鎖
共享式獲取
public final void acquireShared(int arg) {
/* 如果獲取鎖失敗 */
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
private void doAcquireShared(int arg) {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
跟前面不太一樣的地方呢,是setHeadAndPropagate(…)
除了設置頭節點外,主要做的是對後繼的共享模式的節點喚醒。
private void setHeadAndPropagate(Node node, int propagate) {
/* 獲取舊的頭節點 */
Node h = head;
/* 設置新的頭節點 */
setHead(node);
/* 後繼節點嘗試獲取鎖的條件 - 判斷是否有資源 */
/* propagate > 0 表示有資源 */
/* h == null 表示又有設置新的頭節點,舊的頭節點自然是空的,因爲是現在有資源可用,纔可以設置新的頭節點來獲得 */
/* h.waitStatus < 0 表示這個頭節點還沒有釋放資源 */
/* (h = head) == null 表示之前的舊的頭節點爲空,node設置爲頭節點,同樣是現在有資源可用,纔可以設置 */
/* h.waitStatus < 0 表示這個頭節點還沒有釋放資源 */
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null || s.isShared())
/* 喚醒後繼節點,設置狀態爲propagate */
doReleaseShared();
}
}
爲什麼要對新、舊頭節點有這個判斷?我個人認爲,一個是爲了提高效率(舊的頭節點對後繼節點的喚醒,不會因爲有新的頭節點了,而停止,歷任頭節點都會去做這個對後繼節點的喚醒,可以提高效率),另一個是爲了避免極端情況(舊的頭節點還沒有釋放同步狀態,已經開始設置新的頭節點,此時同步狀態還在舊的頭節點這裏)。
共享式釋放
private void doReleaseShared() {
/* 循環保證當前的操作成功,也就是喚醒後繼節點,設置狀態爲propagate */
for (;;) {
Node h = head;
/* 等待隊列中至少存在一個可用節點 */
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
/* 當前如果修改失敗,不等待,直接交給下一次循環處理 */
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
/* 上面的狀態修改成功,喚醒後繼節點 */
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
/* 因爲是共享式,有可能現在有多個線程在執行這個方法,頭節點有可能已經改變了 */
if (h == head)
break;
}
}
與獨佔式的方式的區別呢,顯而易見,增加了對節點的狀態修改爲propagate。此外呢,增加了for(; ; ) 循環以及if (h == head)
break;判斷,是有考慮現在有多個線程在執行,保證當前的線程操作成功,喚醒頭節點的後繼節點