ReectrantLock 和 AQS 是什麼關系
ReectrantLock 是在 AQS 外面包了一層,ReectrantLock 所有的加鎖、解鎖、打斷、取消等等,底層都是交給 AQS完成的。
ReectrantLock 标準使用方式
class X {
private final ReentrantLock lock = new ReentrantLock();
// ...
public void m() {
// 加鎖
lock.lock(); // block until condition holds
try {
// 被加鎖的代碼塊
// ... method body
} finally {
// 解鎖
lock.unlock()
}
}
解鎖過程
public void unlock() {
// 調用非公平鎖的釋放鎖方法。
sync.release(1);
}
public final boolean release(int arg) {
// 釋放鎖,
if (tryRelease(arg)) {
// 拿頭結點。
Node h = head;
// t頭結點不是空,而且頭結點的 waitStatus 此時是 -1。
if (h != null && h.waitStatus != 0)
// 喚醒離頭結點最近的沒有被取消的節點。
// 這裡是傳了頭結點進去。
unparkSuccessor(h);
return true;
}
return false;
}
釋放鎖的細節:
protected final boolean tryRelease(int releases) {
// 這跟鎖重入有關系,當 c == 0 時,才能釋放成功。
// 比如,T0 加鎖四次,getState() == 4,釋放三次 releases ==3
// c=4-3, T0 當然不能釋放成功,還有一層鎖呢。
int c = getState() - releases;
// 如果目前線程不是鎖的持有者,抛異常。
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
// 當 c==0 時,鎖才能釋放成功。
if (c == 0) {
free = true;
// 将同步器中鎖的持有者置為 null。
setExclusiveOwnerThread(null);
}
// CAS 修改 state = 0。
setState(c);
// 傳回
return free;
}
喚醒離頭結點最近的沒有被取消的節點,在頭結點後面 可能有節點(線程)已經被取消了,那就将這些被取消的線程從隊列中移除,這個樣的話,第一個沒有被取消的節點就放在了頭結點後面,也就是隊列中的第二個節點的位置。
// node == head
private void unparkSuccessor(Node node) {
// ws == -1
int ws = node.waitStatus;
if (ws < 0)
// 設定隊頭節點的 waitStatus == 0
compareAndSetWaitStatus(node, ws, 0);
// s 先指向隊列中的第二個節點。
Node s = node.next;
// 如果第二個節點是空,或者被取消了。
if (s == null || s.waitStatus > 0) {
s = null;
// t 從隊尾開始,朝着隊頭的方向,掃一遍隊列中的所有節點。
for (Node t = tail; t != null && t != node; t = t.prev)
// s 指向離隊頭最近的且沒有被取消的線程節點。
if (t.waitStatus <= 0)
s = t;
}
// unpark s 指向的節點中的線程。
if (s != null)
LockSupport.unpark(s.thread);
}
喚醒 node 後,node還要繼續去競争鎖。
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;
}
// 競争失敗,繼續追加到隊列裡 park。
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt()) // 從這裡喚醒的。
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
被喚醒變了怎麼還要去競争鎖,而且還有失敗的可能性?
因為這是非公平鎖,既有被喚醒的,可能同時還有剛産生的線程,兩者會競争鎖。
總結
釋放鎖過程做三件事情:
- 釋放鎖
- 喚醒離頭結點最近的第一個沒有被取消的 node。
- 被喚醒的 node 再去競争鎖,成功,node出隊執行邏輯代碼,失敗,繼續追加到隊列裡 park。