一、簡介
AbstractQueuedSynchronizer簡稱AQS,從名字理解,它是一個抽象隊列同步鎖,是重入鎖、讀寫鎖的基類。如果我們要全面學習java的併發鎖,首先要掌握AQS的加鎖機制。下面文章我將介紹AQS的結構,加鎖和解鎖方法。
二、什麼是AQS
1.AQS初認識
AQS的加鎖原理可以用下面的圖表示,每次調用加鎖時,會對int類型的成員變量state進行判斷。
加鎖:當state不等於0時,會把當前節點加到鏈表中,阻塞等待,直到被前驅喚醒,head指向自己時纔會去爭取鎖。爭取鎖的時候是用了unsafe.compareAndSwapInt方法,其實AQS裏面使用了大量Unsafe裏的方法,它是不安全的,我們一般很少直接調用,但是人家Doug Lea大神卻使用得遊刃有餘。
當state等於0,並且調用compareAndSwapInt方法成功時,表示獲得了鎖。
解鎖:獲得鎖的線程在調用解鎖方法後,會把head指向到下一個節點,並且喚醒下一個節點去競爭獲取鎖。
2.AQS類關係圖
從關係圖中可以看到AQS繼承了AbstractOwnableSynchronizer,與AbstractQueuedLongSynchronizer並列關係。
AbstractOwnableSynchronizer主要有兩個方法,用戶保存和獲取佔用鎖的線程。
AbstractQueuedLongSynchronizer裏面的方法實現基本與AbstractQueuedSynchronzier相同,名字的差別在於Long,其實也就是他的state成員變量是Long類型。它作用表現在可以保存更多的狀態位,在鎖重入、讀寫鎖的數量方面有更多的選擇。估計很少場景需要用到它。
3.基本數據結構
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
static final class Node {
/** 指明一個節點是共享節點,可以理解是讀鎖 */
static final Node SHARED = new Node();
/** 排他節點,可以理解爲寫鎖 */
static final Node EXCLUSIVE = null;
/** 代表節點的線程已經被取消 */
static final int CANCELLED = 1;
/** 信號量,代表後繼節點需要阻塞等待 */
static final int SIGNAL = -1;
/** 代表線程在另外一個隊列或者鏈表中等待,可以把condition理解成第二等待隊列,只有從第二隊列移到第一隊列才能參與鎖的競爭 */
static final int CONDITION = -2;
/** 傳播狀態,指示後繼節點可以無條參與讀鎖的競爭 */
static final int PROPAGATE = -3;
/** 等待狀態,爲以上幾種類型 */
volatile int waitStatus;
/** 上一個節點 */
volatile Node prev;
/** 下一個節點 */
volatile Node next;
/** 當前線程 */
volatile Thread thread;
/** 在conditin等待隊列中,指向下一個等待節點 */
Node nextWaiter;
}
// 可以理解爲它就是第二等待隊列
public class ConditionObject implements Condition, java.io.Serializable {
/** 指向第一個等待節點 */
private transient Node firstWaiter;
/** 指向最後一個等待節點. */
private transient Node lastWaiter;
}
}
三、AQS的使用
1.加鎖
// 一般加鎖會首先調用這個方法,參數爲加鎖的計數器,累加到成員變量state
public final void acquire(int arg) {
// 如果不能立刻,則加入到隊列中去獲取
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
// 子類實現,一般是state成員變量加上arg
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
// 這裏把一個節點加到隊列末端
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;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
// 入隊列
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;
}
}
}
}
// 等待隊列的節點獲取到鎖會返回true,否則阻塞等待
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
// 如果上一個節點是頭節點,說明可以參與鎖競爭了。並且鎖競爭成功則當前節點佔用鎖,把head指向自己
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
// 競爭鎖失敗,判斷是否阻塞線程,下面分析這個方法
if (shouldParkAfterFailedAcquire(p, node) &&
// 這裏是阻塞線程,用的是LockSupport.park(this)
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
// 失敗則說明是有異常了,則取消該線程的等待。後面分析這方面
cancelAcquire(node);
}
}
// 競爭鎖失敗時,判斷線程是否需要阻塞
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
// 前驅狀態時signal,則需要阻塞等待喚醒
return true;
if (ws > 0) {
// 前驅狀態大於0,說明是取消的狀態,則向前遍歷找到不大於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.
*/
// 到這一步說明前驅的狀態要麼是0,要麼是PROPAGATE(傳播狀態),則當前線程可以再次參與鎖競爭一次,如果第二次還沒有獲取到,則前驅狀態變爲sigal了會到第一步阻塞線程
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
// 取消鎖競爭
private void cancelAcquire(Node node) {
// Ignore if node doesn't exist
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;
// 如果當前節點是尾節點,則設置前驅爲尾節點
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.
// 如果前驅不爲頭節點,並且(前驅狀態是sigal,或者狀態小於0並且設置狀態sigal成功),並且前驅線程不爲null,則設置前驅的下一個節點爲當前節點的下一個節點
int ws;
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 {
// 到這步說明需要參與鎖競爭了
unparkSuccessor(node);
}
node.next = node; // help GC
}
}
2.解鎖
// 解鎖相對簡單
public final boolean release(int arg) {
// 這裏一般是狀態state減去arg
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
// 喚醒後繼節點
unparkSuccessor(h);
return true;
}
return false;
}
四、總結
AQS是重入鎖和同步隊列的基礎類,是學習JUC的第一課。以上是我對AQS的瞭解,如有不對之處歡迎指正。