引言
可能我們平常很少聽說它,就算是在并發里也很少用到它,但是我們所用的Condition(條件)、ReentrantLock(可重入鎖)、ReentrantReadWriteLock(讀寫鎖)就是根據它所構建的,它叫做隊列同步器,是構建其他鎖和同步組件的基礎框架。
同步器的主要使用方法是繼承,子類通過繼承同步器并實現它的抽象方法來管理同步狀態,在抽象方法的實現過程中免不了對同步狀態進行更改,就需要使用同步器提供的3個最主要的方法
protected final int getState() ; //獲取同步狀態
protected final void setState(int newState) ;//設置同步狀態
protected final boolean compareAndSetState(int expect, int update) ;//原子性設置同步狀態(使用CAS)
AQS本身是沒有實現任何同步接口的,它僅僅是定義了若干同步狀態的獲取和釋放來供自定義同步組件使用,它可以支持獨占式的獲取同步狀態(只能有一個線程使用,其他都得等待),也支持共享式的獲取同步狀態(如讀寫鎖中的讀鎖)
同步器的設計是采用的模板方法設計模式,我們只需要重寫指定的方法,隨后將同步器組合在自定義同步組件中,并調用模板方法提供的模板方法即可。
源碼分析
AQS里面可以重寫的主要方法如下(需要我們自己實現邏輯):
protected boolean tryAcquire(int arg) ; //獨占式的獲取鎖
protected boolean tryRelease(int arg) ;//獨占式的釋放鎖
protected int tryAcquireShared(int arg) ;//共享式的獲取鎖
protected boolean tryReleaseShared(int arg) ; //共享式的釋放鎖
protected boolean isHeldExclusively() ; //判斷當前線程是否是在獨占模式下被線程占用,一般表示是否被當前線程所占用
AQS提供的主要模板方法如下(我們不能重寫):
public final void acquire(int arg) ; //獨占式獲取鎖
public final void acquireInterruptibly(int arg); //與acquire(int arg)一樣,但響應中斷
public final boolean tryAcquireNanos(int arg, long nanosTimeout);//在acquireInterruptibly(int arg基礎上加入了超時機制,在規定時間內沒有獲取到鎖返回false
獲取到了返回true
public final boolean release(int arg) ;//獨占式的釋放同步狀態
public final void acquireShared(int arg) ; //共享式的獲取鎖
public final void acquireSharedInterruptibly(int arg); //在acquireShared(int arg)加入了對中斷的響應
public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout) //在acquireSharedInterruptibly(int arg)基礎上加入超時機制
public final boolean releaseShared(int arg) ; //共享式的釋放鎖
這上面的所有方法都加上了final,表明了它們不可繼承,這就是模板方法設計模式
下面我們來看一下它的源碼
- 首先是獨占式同步鎖的獲取
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
這是獨占式獲取鎖的入口
具體流程如下:
1、用tryacquire(arg)去獲取同步狀態(需要我們自己實現)
2、獲取不到把線程封裝Node節點(addwaiter),在加入等待隊列中
- tryAcquire(int arg)源碼
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
源碼就是簡單地拋出異常,所以需要我們自己去實現
- 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;
//嘗試加入到隊尾,因為這可能有多個線程競爭,采用compareAndSetTail(CAS操作)
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//如果表頭為空又或者節點沒加入到隊尾
enq(node);
return node;
}
先構造成Node節點
- Node是其內部類,主要構造如下
static final class Node {
volatile int waitStatus;
volatile Node prev;
volatile Node next;
volatile Thread thread;
Node nextWaiter;
}
prev:前驅節點;
next:后繼節點;
thread:進入隊列的當前線程
-
nextWaiter:存儲condition隊列中的后繼節點。
- waitStatus:節點狀態,主要有這幾種狀態:
1、 CANCELLED:當前線程被取消;
2、SIGNAL:當前節點的后繼節點需要運行;
3、 CONDITION:當前節點在等待condition
4、PROPAGATE:當前場景下后續的acquireShared可以執行;
- waitStatus:節點狀態,主要有這幾種狀態:
隊列的基本結構如下:
- enq源碼
/**
* 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;
//通過compareAndSetTail保證節點能被線程安全添加
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
設置尾節點的過程
加入到同步隊列中之后,就執行 acquireQueued
- acquireQueued源碼
/**
* 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();
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);
}
}
節點進入同步隊列后,就開始自旋,每個節點都在觀察自己是否滿足條件,當“條件”滿足,就可以獲取同步狀態,然后從自旋地過程中退出
這里有兩點要說明:
1、頭節點是獲取到同步狀態的點,而頭節點的線程釋放同步狀態后,會喚醒其后續節點,所以每個節點都在判斷自己的前驅節點是否是頭節點,是之后看能不能獲取到同步狀態,只有兩者都滿足時,才能退出
2、維護同步隊列的是fifo原則(先進先出),整個過程如圖所示:
總結一下獨占式獲取鎖的流程:
當前線程獲取到同步狀態后,就需要釋放同步狀態,使后續節點能夠獲取,主要是通過release(int arg) 實現的
- release(int arg) 源碼
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
tryrelease:判斷能否釋放同步狀態
unparkSuccessor(h):喚醒后續節點
上面講了獨占式獲取獲取鎖,下面講講共享式獲取
共享式和獨占式最大的區別是同一時刻能否有多個線程同時獲取到同步狀態,如圖所示
共享式獲取的入口是acquireShared 方法
- acquireShared 源碼
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
tryAcquireShared方法是需要我們自己實現的,返回的是int值,當返回值大于0時,表示能獲取到同步狀態
- 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);
}
}
先把線程加入到同步隊列中,然后死循環,判斷前驅節點是否是頭節點,然后獲取同步狀態,當兩者都滿足是就退出循環
與獨占式獲取同步狀態一樣,共享式獲取也是需要釋放同步狀態的,AQS提供releaseShared(int arg)方法可以釋放同步狀態。
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
1、調用tryReleaseShared方法釋放狀態;
2、 調用doReleaseShared方法喚醒后繼節點;