aqs
AQS(AbstractQueuedSynchronizer)是JAVA中衆多鎖以及併發工具的基礎,其底層採用樂觀鎖,大量使用了CAS操作, 並且在衝突時,採用自旋方式重試,以實現輕量級和高效地獲取鎖。
AQS雖然被定義爲抽象類,但事實上它並不包含任何抽象方法。AQS是被設計爲支持多種用途,如果定義抽象方法,子類在繼承的時候就需要實現所有抽象方法,所以AQS將需要子類覆蓋的方法都設計爲protect方法,默認拋出UnsupportedOperationException異常,。如果子類用到這些方法就必須重寫,否則會拋出異常,如果沒有用到則不需要做任何操作。
AbstractQueuedSynchronizer只繼承了AbstractOwnableSynchronizer,實現了java.io.Serializable接口。
AbstractOwnableSynchronizer類是一種同步器,這個類僅有set和get獨佔線程資源。
public abstract class AbstractOwnableSynchronizer implements java.io.Serializable { private static final long serialVersionUID = 3737899427754241961L; protected AbstractOwnableSynchronizer() { } private transient Thread exclusiveOwnerThread; protected final void setExclusiveOwnerThread(Thread t) { exclusiveOwnerThread = t; } protected final Thread getExclusiveOwnerThread() { return exclusiveOwnerThread; } }
exclusiveOwnerThread 即獨佔資源的線程。
AQS原理
AQS維護了一個state變量和node雙向鏈表。
state是已獲取資源佔有許可的數量。例如線程調用acquire(1)獲取資源的許可,acquire會調用一次tryAcquire(1)獲取資源。如果獲取成功,則state加1並且調用父類的設置獨佔線程將當前線程設置爲獨佔線程。如果獲取失敗,則說明已經有線程佔用了這個資源,需要等待佔用釋放。此時將該線程封裝成node節點,加入雙向鏈表,之後Locksupport.pack()堵塞當前線程。如果這個線程被喚醒則繼續循環調用tryAcquire獲取許可,如果獲取到了將自己的node節點設置爲鏈表的頭結點並把之前的頭結點去掉。如果線程釋放資源,調用release方法,release方法會調用tryRelease方法嘗試釋放資源,如果釋放成功,則state減1,再調用AQS的父類AbstractOwnableSynchronizer的設置獨佔線程爲null,再locksupport.unpack()雙向node鏈表的頭node節點的線程,恢復其執行。
源碼
成員變量
private transient volatile Node head;
private transient volatile Node tail;
private volatile int state;
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long stateOffset;
private static final long headOffset;
private static final long tailOffset;
private static final long waitStatusOffset;
private static final long nextOffset;
static {
try {
stateOffset = unsafe.objectFieldOffset
(AbstractQueuedSynchronizer.class.getDeclaredField("state"));
headOffset = unsafe.objectFieldOffset
(AbstractQueuedSynchronizer.class.getDeclaredField("head"));
tailOffset = unsafe.objectFieldOffset
(AbstractQueuedSynchronizer.class.getDeclaredField("tail"));
waitStatusOffset = unsafe.objectFieldOffset
(Node.class.getDeclaredField("waitStatus"));
nextOffset = unsafe.objectFieldOffset
(Node.class.getDeclaredField("next"));
} catch (Exception ex) { throw new Error(ex); }
}
有一個頭尾節點和state變量,實現CAS的Unsafe的工具類,還有一些偏移量,都用於Unsafe的CAS操作,通過靜態代碼塊進行初始化,通過objectFieldOffset獲取對應字段相對於該對象的起始地址的偏移量。
Node節點
static final class Node {
//共享模式
static final Node SHARED = new Node();
//獨佔模式
static final Node EXCLUSIVE = null;
//當線程等待超時或者被中斷,則取消等待
static final int CANCELLED = 1;
//後繼節點處於等待狀態,當前節點(爲-1)被取消或者中斷時會通知後繼節點,使後繼節點的線程得以運行
static final int SIGNAL = -1;
//當前節點處於等待隊列,節點線程等待在Condition上,當其他線程對condition執行signall方法時,等待隊列轉移到同步隊列,加入到對同步狀態的獲取。
static final int CONDITION = -2;
//與共享模式相關,在共享模式中,該狀態標識結點的線程處於可運行狀態。
static final int PROPAGATE = -3;
//狀態
volatile int waitStatus;
//上一個節點
volatile Node prev;
//下一個節點
volatile Node next;
//節點所代表的線程
volatile Thread thread;
//Node既可以作爲同步隊列節點使用,也可以作爲Condition的等待隊列節點使用
Node nextWaiter;
final boolean isShared() {
return nextWaiter == SHARED;
}
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
Node() { // Used to establish initial head or SHARED marker
}
Node(Thread thread, Node mode) { // Used by addWaiter
this.nextWaiter = mode;
this.thread = thread;
}
Node(Thread thread, int waitStatus) { // Used by Condition
this.waitStatus = waitStatus;
this.thread = thread;
}
}
enq方法
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;
}
}
}
}
enq方法是將node加入鏈表,如果tail尾節點爲空則必須進行初始化,如果tail不爲空,則將node的前指針指向tail,通過CAS將tail的指向改爲node,然後設置t.next爲node,完成node插入鏈表尾部。
addWaiter方法
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;
}
addWaiter方法包裝node節點,放入node雙向鏈表。如果tail不爲空則說明初始化過了直接將node加入鏈表尾部,如果爲空則進行初始化再將node加入鏈表尾部。
共享模式
acquireShared(獲取鎖)
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
嘗試去獲取資源,如果沒有獲取資源返回負數,tryAcquireShared方法需要子類自己去實現,如果不實現會直接拋異常(在讀寫鎖的Sync實現);如果沒有獲取到資源加入等待隊列等待獲取資源。
doAcquireShared
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);
}
}
先吧當前節點加入到隊列尾部,然後進入自旋,自旋的目的是爲了獲取資源或者阻塞,如果此節點的前一個node是head節點,就去獲取資源,如果獲取失敗就執行shouldParkAfterFailedAcquire,將前一個node設置爲SIGNAL,獲取成功就setHeadAndPropagate。
setHeadAndPropagate
////兩個入參,一個是當前成功獲取共享鎖的節點,一個就是tryAcquireShared方法的返回值,它可能大於0也可能等於0
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // Record old head for check below
//設置新的頭節點
setHead(node);
//propagate > 0 代表還有資源,還可以繼續喚醒 | h.waitStatus 是 -1 or -3
if (propagate > 0 || h == null || h.waitStatus < 0) {
Node s = node.next;
//如果當前節點的後繼節點是共享類型獲取沒有後繼節點,則進行喚醒
if (s == null || s.isShared())
doReleaseShared();
}
}
會喚醒後面的所有節點
doReleaseShared(喚醒)
private void doReleaseShared() {
for (;;) {
//從頭結點開始 head已是上面設置的head節點
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
//表示需要喚醒(-1)
if (ws == Node.SIGNAL) {
//CAS 將狀態置爲0
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
//喚醒
unparkSuccessor(h);
}
//如果後繼節點暫時不需要喚醒,則把當前節點狀態設置爲PROPAGATE確保以後可以傳遞下去
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
//如果頭結點沒有發生變化,表示設置完成,退出循環
//如果頭結點發生變化,比如說其他線程獲取到了鎖,爲了使自己的喚醒動作可以傳遞,必須進行重試
if (h == head) // loop if head changed
break;
}
}
unparkSuccessor方法
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
//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);
}
用unpark()喚醒等待隊列中最前邊的那個未放棄線程,node的waitStatus爲signal或condition,則可以喚醒,先重置node的waitStatus爲0(允許失敗),找到下一個需要喚醒的節點喚醒。
從後往前找是因爲下一個任務有可能被取消了,節點就有可能爲null
shouldParkAfterFailedAcquire
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 {
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
主要是進行的狀態的檢查,如果前一個節點的狀態是-1則返回true;如果前一個節點取消了,那就向前找到一個沒有被取消的節點,將取消的節點捨棄,如果前一個節點沒有被取消則將節點狀態設置爲-1.
releaseShared( 釋放鎖)
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
獨佔模式
acquire(獲取鎖)
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
首先也是嘗試獲取資源,如果獲取到資源則直接返回了,如果沒有獲取到資源則執行acquireQueued(addWaiter(Node.EXCLUSIVE), arg),將該線程加入隊列節點尾部。
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);
}
}
和共享模式類似,先獲取該節點的前一個節點,如果前一個節點是頭結點就嘗試獲取資源。如果獲取到資源則把這個接地點設爲頭節點 直接返回了;如果沒有獲取到資源則進入阻塞掛起。
掛起邏輯同上。
cancelAcquire
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的狀態設置爲1 其他節點在處理時就可以跳過
node.waitStatus = Node.CANCELLED;
//如果是尾節點直接刪除返回
if (node == tail && compareAndSetTail(node, pred)) {
compareAndSetNext(pred, predNext, null);
} else {
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)
//刪除該node
compareAndSetNext(pred, predNext, next);
} else {
//要麼當前節點的前置節點是頭結點,直接喚醒當前節點的後繼節點
unparkSuccessor(node);
}
node.next = node; // help GC
}
}
獲取鎖資源失敗的處理,即自己實現的獲取資源的邏輯出異常的時候會進入到這裏。(共享模式同這裏的)
release(釋放鎖)
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
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);
}
條件隊列(ConditionObject)
使用場景
//首先創建一個可重入鎖,它本質是獨佔鎖
private final ReentrantLock takeLock = new ReentrantLock();
//創建該鎖上的條件隊列
private final Condition notEmpty = takeLock.newCondition();
//使用過程
public E take() throws InterruptedException {
//首先進行加鎖
takeLock.lockInterruptibly();
try {
//如果隊列是空的,則進行等待
notEmpty.await();
//取元素的操作...
//如果有剩餘,則喚醒等待元素的線程
notEmpty.signal();
} finally {
//釋放鎖
takeLock.unlock();
}
//取完元素以後喚醒等待放入元素的線程
}
Condition一般都是配合一個顯式鎖Lock一起使用,Lock接口的方法中有一個newCondition()方法用於生成Condition對象。
通過ReentrantLock的lock方法,如果獲取不到鎖當前線程會進入AQS隊列阻塞;被喚醒後繼續獲取鎖,如果獲取到鎖,移出AQS隊列,繼續執行;遇到Condition的await方法,加入“條件隊列”,阻塞線程;被其他線程的signal方法喚醒,從“條件隊列”中刪除,並加入到AQS隊列,如果獲取到鎖就繼續執行。可以看到上述操作,線程節點(Node)其實在兩個隊列之間切換,由於“條件隊列”在被喚醒時 都是從頭開始遍歷,所以只需要使用單向鏈表實現即可。
public interface Condition {
void await() throws InterruptedException;
void awaitUninterruptibly();
long awaitNanos(long nanosTimeout) throws InterruptedException;
boolean await(long time, TimeUnit unit) throws InterruptedException;
boolean awaitUntil(Date deadline) throws InterruptedException;
void signal();
void signalAll();
}
ConditionObject 實現了 Condition接口,Condition接口中一個有7個接口:
- await : 使用這個鎖必須放在一個顯式鎖的lock和unlock之間,調用該方法後當前線程會釋放鎖並被阻塞,直到其他線程通過調用同一個Condition對象的signal或者signalAll方法或被中斷,再次被喚醒。(可被中斷)
- awaitUninterruptibly : 此方式是不可被中斷的,只能通過其他線程調用同一個Condition對象的signal或者signalAll方法,才能被喚醒。(不響應中斷)
- awaitNanos : 等待納秒時間
- await(long time, TimeUnit unit) : 等待一個指定時間
- awaitUntil : 等待直到一個截止時間
- signal : 喚醒等待隊列中的第一個節點
- signalAll : 喚醒等待隊列中的所有節點
await
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
//加入條件隊列
Node node = addConditionWaiter();
//釋放當前線程佔用的排它鎖
int savedState = fullyRelease(node);
int interruptMode = 0;
//節點不在AQS的阻塞隊列中
while (!isOnSyncQueue(node)) {
//阻塞該線程
LockSupport.park(this);
//判斷中斷標記在阻塞等待期間 是否改變
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
//當被喚醒後,該線程會嘗試去獲取鎖,只有獲取到了纔會從await()方法返回,否則的話,會掛起自己
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
//清理取消節點對應的線程
unlinkCancelledWaiters();
if (interruptMode != 0)
//拋出中斷異常,或者重新進入中斷
reportInterruptAfterWait(interruptMode);
}
先將該節點加入到條件隊列,然後釋放掉當前的鎖,如果該節點不在AQS的阻塞隊列中就阻塞該線程,等待signal;被喚醒後該線程會嘗試去獲取鎖
signal
public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
//第一個節點
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
private void doSignal(Node first) {
do {
//firstWaiter 下一個節點
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
final boolean transferForSignal(Node node) {
//改變線程狀態
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
//加入AQS阻塞隊列
Node p = enq(node);
int ws = p.waitStatus;
//喚醒
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
將條件隊列的第一個節點移除,加入到AQS的阻塞隊列中。
signalAll
public final void signalAll() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignalAll(first);
}
private void doSignalAll(Node first) {
lastWaiter = firstWaiter = null;
do {
Node next = first.nextWaiter;
first.nextWaiter = null;
transferForSignal(first);
first = next;
} while (first != null);
}
signalAll 會遍歷全部節點喚醒加入到AQS阻塞隊列。
條件隊列與同步隊列
1.同步隊列依賴一個雙向鏈表來完成同步狀態的管理,當前線程獲取同步狀態失敗 後,同步器會將線程構建成一個節點,並將其加入同步隊列中。
2.通過signal或signalAll將條件隊列中的節點轉移到同步隊列。(由條件隊列轉化爲同步隊列)
條件隊列節點來源:
- 調用await方法阻塞線程;
- 當前線程存在於同步隊列的頭結點,調用await方法進行阻塞(從同步隊列轉化到條件隊列)
例如:
- 假設初始狀態如下,節點A、節點B在同步隊列中。
-
節點A的線程獲取鎖權限,此時調用await方法。節點A從同步隊列移除, 並加入條件隊列中。
-
調用 signal方法,從條件隊列中取出第一個節點,並加入同步隊列中,等待獲取資源