從自定義獨佔鎖OnlyOneLock去看AQS源碼
先說明本文是基於JDK1.8的源碼分析,其他版本的JDK實現可能有所不同。
在自定義鎖中,我們覆蓋Lock接口的方法,AQS中我們覆蓋的幾個方法只需要處理好拿到鎖和沒有拿到鎖的邏輯,至於線程怎麼維護,我們卻不需要理會。
AQS如何實現獲取鎖的邏輯
我們自定義的獨佔鎖OnlyOneLock,在我們重寫了tryAcquire時,AQS的模板方法acquire內部會調用這個tryAcquire方法,然後維護隊列邏輯。
OnlyOneLock類的tryLock方法
@Override
public boolean tryLock() {
return aqs.tryAcquire(1);
}
tryLock調用了AQS的tryAcquire方法。剛好我們在OnlyOneLockAQS類中重寫了這個方法
@Override
protected boolean tryAcquire(int arg) {
//狀態爲0時,設置爲1,表示當前線程獲取到鎖
if(compareAndSetState(0,1)){
//設置當前線程爲獲取到鎖的線程
setExclusiveOwnerThread(Thread.currentThread());
return true ;
}
return false;
}
AQS的模板方法acquire內部會調用這個tryAcquire方法,然後維護隊列邏輯。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) //調用
tryAcquire成功獲取鎖的話,就結束 selfInterrupt(); //沒有獲取成功,調用addWaiter();把當前線程構建成一個Node加入到隊列尾部。
}
Node.EXCLUSIVE是一個null結點。因爲我們這裏定義的是獨佔鎖。
上面調用了addWaiter方法。把當前線程構建成一個Node加入到隊列尾部。
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode); //新的結點
// Try the fast path of enq; backup to full enq on failure
Node pred = tail; //tail是原隊列的尾結點
if (pred != null) { //原隊列不爲空
node.prev = pred; //新節點node的前置結點是pred(原隊列的尾結點)
if (compareAndSetTail(pred, node)) { //連接新的尾結點
pred.next = node;
return node;
}
}
enq(node); //原隊列爲空的情況下,初始化隊列
return node;
}
調用addWaiter方法後,會再調用acquireQueued。
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor(); //獲取node結點的前一個結點p
if (p == head && tryAcquire(arg)) { //如果p是首節點head並且node代表的線程獲取鎖成功
setHead(node); //新的隊首
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt()) //等待
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
acquireQueued方法可以總結如下:
一,先檢查當前線程在隊列中的前一個線程是否爲頭結點,如果是就用tryAcquire方法嘗試再拿一次。
二,上面的嘗試中沒有拿到鎖的話,會在parkAndCheckInterrupt時把自己給Park掉,Park大致可以理解爲休眠(Sleep)
三,Park的時候,線程就老老實實的在隊列裏面等着,等什麼呢?等獲取鎖的線程釋放鎖後通知它,它好繼續在for循環裏面獲取鎖。
自定義的獨佔鎖AQS維護鎖邏輯的大致過程流程圖如下。
因爲我們定義的是獨佔鎖,如果線程請求鎖直接成功,那它就不需要入隊列。如果沒有成功獲取,就要入隊列,隊列的首節點永遠是持有鎖的那個結點。
AQS的內部類Node有一個重要的成員變量waitStatus。該變量用來表示當前節點代表的線程的狀態。初始時爲0。
* The field is initialized to 0 for normal sync nodes, and
* CONDITION for condition nodes. It is modified using CAS
* (or when possible, unconditional volatile writes).
*/
volatile int waitStatus;
以下是他的取值可能。
static final int CANCELLED = 1; //取消
static final int SIGNAL = -1; //該線程需要unpark
static final int CONDITION = -2; //等待某個條件
static final int PROPAGATE = -3;
OnlyOneLock重寫的unlock調用了AQS的release方法。
@Override
public void unlock() {
aqs.release(1);
}
AQS會調用tryRelease方法,因爲我們在OnlyOneLockAQS中重寫了這個方法,所以執行時,調用的是我們重寫後的tryRelease方法。
public final boolean release(int arg) {
if (tryRelease(arg)) { //如果釋放鎖成功
Node h = head; //隊頭
if (h != null && h.waitStatus != 0) //
waitStatus!=0說明,線程在等待獲取鎖 unparkSuccessor(h); //喚醒後繼節點
return true;
}
return false;
}
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); //喚醒後繼結點
}
參考:
https://juejin.im/post/5a3a09d9f265da4312810fb9
https://juejin.im/post/5a3c6aa551882538d3101d5f