AQS--獨佔鎖原始碼解析
阿新 • • 發佈:2018-12-10
- AQS獨佔鎖是很多併發包的基礎,像讀寫鎖,CountDownLatch都是基於AQS實現的,搞懂其原理對我們學習java併發包會有很好的作用。 - 先來看鎖的幾種狀態
volatile int waitStatus; //鎖狀態
//以下幾種狀態代表鎖的具體值 static final Node EXCLUSIVE = null;//代表獨佔鎖模式 static final int CANCELLED = 1;//節點被取消 static final int SIGNAL = -1;//代表喚醒後續節點 static final int CONDITION = -2;//指定條件下傳播 static final int PROPAGATE = -3; //當前節點無條件向後傳播
- 獨佔鎖獲取鎖過程: acquire(int arg)方法是我們獲取獨佔鎖的入口,看方法註釋我們可以知道,該方法是獲取獨佔鎖的模式,如果成功的話,執行當前執行緒,否則的話會將當先執行緒放置於一個佇列中,tryAcquire(arg)方法是我們是具體實現類實現獲取鎖的的方式,如果返回false也就是當前執行緒獲取鎖失敗,則會執行*acquireQueued()*將當前執行緒執行加入佇列
/** * Acquires in exclusive mode, ignoring interrupts. Implemented * by invoking at least once {@link #tryAcquire}, * returning on success. Otherwise the thread is queued, possibly * repeatedly blocking and unblocking, invoking {@link * #tryAcquire} until success. This method can be used * to implement method {@link Lock#lock}. * * @param arg the acquire argument. This value is conveyed to * {@link #tryAcquire} but is otherwise uninterpreted and * can represent anything you like. */ public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }
獲取鎖失敗後將當前執行緒加入佇列:
/** * Creates and enqueues node for current thread and given mode. * * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared * @return the new 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; if (compareAndSetTail(pred, node)) { pred.next = node; return node; } } //完整的入隊操作 enq(node); return node; }
完整的入隊操作:
/**
* Inserts node into queue, initializing if necessary. See picture above.
* @param node the node to insert
* @return node's predecessor
*/
private Node enq(final Node node) {
for (;;) {
Node t = tail;
//當佇列為空,新建佇列
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
//直接將當前節點插入隊尾
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
當我們將之前步驟的執行緒加入佇列後,就需要執行喚醒,或者繼續迴圈的操作,以下是具體的實現
/**
* Acquires in exclusive uninterruptible mode for thread already in
* queue. Used by condition wait methods as well as acquire.
*
* @param node the node
* @param arg the acquire argument
* @return {@code true} if interrupted while waiting
*/
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
//獲取當前節點的前置節點
final Node p = node.predecessor();
//若前驅節點為頭節點,並且已經獲取鎖,也就是tryAcquire(arg)為true,則將當前節點設定為頭節點,退出迴圈
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
//如果獲取鎖失敗的情況,將掛起當前執行緒,並且執行中斷操作,中斷成功的話更新 interrupted = true;
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
獲取鎖失敗後的處理:
/**
* Checks and updates status for a node that failed to acquire.
* Returns true if thread should block. This is the main signal
* control in all acquire loops. Requires that pred == node.prev.
*
* @param pred node's predecessor holding status
* @param node the node
* @return {@code true} if thread should block
*/
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
//獲取前置節點的狀態為SIGNAL,也就是代表喚醒後續節點,之後繼續執行上一步的死迴圈,直到當前執行緒獲取鎖。
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
//如果前置節點>0也就一種情況,就是該節點的狀態為取消狀態
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
//向前替換當前節點的前置節點,直到滿足當前節點的前置節點的狀態不大於0
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
//否則的話,將pred的waitStatus 狀態為 0 或者為 PROPAGATE.更新為SIGNAL
//也是與我們後續處理狀態為0的操作做鋪墊--(重點!)
} 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;
}
執行緒中斷操作:
/**
* Convenience method to park and then check if interrupted
*
* @return {@code true} if interrupted
*/
private final boolean parkAndCheckInterrupt() {
//沒啥看頭,就是中斷當前執行緒.
LockSupport.park(this);
return Thread.interrupted();
}
最後在finally塊中對獲取失敗後的一些措施:
/**
* Cancels an ongoing attempt to acquire.
*
* @param node the node
*/
private void cancelAcquire(Node node) {
// 如果當前節點為空直接返回
if (node == null)
return;
//清空當前節點的執行緒
node.thread = null;
// 跳過無效的節點,也就是pred.waitStatus > 0的節點
Node pred = node.prev;
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
// predNext is the apparent node to unsplice. CASes below will
// fail if not, in which case, we lost race vs another cancel
// or signal, so no further action is necessary.
Node predNext = pred.next;
// Can use unconditional write instead of CAS here.
// After this atomic step, other Nodes can skip past us.
// Before, we are free of interference from other threads.
//將節點狀態設定為取消
node.waitStatus = Node.CANCELLED;
// If we are the tail, remove ourselves.
//當前節點是尾節點的話直接刪除
if (node == tail && compareAndSetTail(node, pred)) {
compareAndSetNext(pred, predNext, null);
//如果當前節點的存在後續的節點需要喚醒(也就是當前節點不是尾節點),我們將喚醒其後續節點。
} else {
// If successor needs signal, try to set pred's next-link
// so it will get one. Otherwise wake it up to propagate.
int ws;
//如果當前節點的前驅節點不為頭並且ws==SIGNAL,
//那麼就將當前節點的前節點,與當前節點的後節點連在一起,相當去刪除當前節點
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);
//剩下的else也就是pred為頭,或者ws==PROPAGATE或0
} else {
//喚醒node的後續節點
unparkSuccessor(node);
}
node.next = node; // help GC
}
}
喚醒後續節點(也就是當前節點)操作(註釋也有✌️標註):
/**
* Wakes up node's successor, if one exists.
*
* @param node the node
*/
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.
*/
//將當前節點狀態更新為0,也就是釋放;
int ws = node.waitStatus;
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.
*/
//喚醒後續節點
Node s = node.next;
//後續節點為空或者是取消狀態(s.waitStatus > 0),清空後續節點(s = null);
if (s == null || s.waitStatus > 0) {
s = null;
//從尾節點往前遍歷,找到最近的有效節點(也就是上一步s == null || s.waitStatus > 0的後續),最後進行喚醒操作。
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
//喚醒
LockSupport.unpark(s.thread);
}
- 再來看鎖釋放操作:
/**
* Releases in exclusive mode. Implemented by unblocking one or
* more threads if {@link #tryRelease} returns true.
* This method can be used to implement method {@link Lock#unlock}.
*
* @param arg the release argument. This value is conveyed to
* {@link #tryRelease} but is otherwise uninterpreted and
* can represent anything you like.
* @return the value returned from {@link #tryRelease}
*/
public final boolean release(int arg) {
//這個判斷我們當前狀態為獲取鎖的狀態,不然沒鎖釋放毛毛啊
if (tryRelease(arg)) {
Node h = head;
//特地說明下h.waitStatus != 0是因為我們釋放鎖後會將節點waitStatus更新為0,而且掛起鎖的時候會將狀態為0的更新為SIGNAL,所以不考慮這個條件
if (h != null && h.waitStatus != 0)
//從頭節點開始釋放操作
unparkSuccessor(h);
return true;
}
return false;
}
釋放鎖的具體操作:
/**
* Wakes up node's successor, if one exists.
*
* @param node the node
*/
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.
*/
//如果當前狀態<0,也就是正在獲取的鎖中。則通過CAS將起更改為0(CAS規定了0為初始化狀態),釋放當前執行緒
int ws = node.waitStatus;
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.
*/
//喚醒後繼節點,但是要保證後繼節點是有效的也就是 要滿足 if (t.waitStatus <= 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);
}
最後總結下AQS鎖的大體流程:在多執行緒的環境下,會將爭奪共享資源的執行緒維持在一個FIFO的佇列中進行自旋,如果某個執行緒獲取到鎖並且前驅節點為頭節點,那麼就結束當前執行緒返回。而其他未獲取鎖的執行緒,會繼續維持在佇列,等待下次呼叫。