AbstractQueuedSynchronizer
基礎的同步器,主要實現了對等待隊列的管理(入隊出隊,獨佔/共享模式下對等待節點的喚醒等),而具體的獲取、釋放的判斷邏輯由子類實現
等待隊列
節點類是AbstractQueuedSynchronizer一個內部類Node
該隊列是一個雙向隊列,AQS的head變量指向頭結點(不是等待節點,head.next纔是隊列中第一個等待節點),tail變量指向尾節點(等待節點)
而入隊、出隊操作,即爲對tail和head的更新操作均爲通過Unsafe的CAS操作進行更新
隊列是延遲初始化,在加入節點的時候才進行初始化
acquire、release
AQS中提供了state屬性(int)作爲用於同步的狀態
具體判斷能否獲取、釋放的邏輯方式(基於state屬性)都是abstract(如
tryAcquire,tryAcquireShared,tryRelease,
tryReleaseShared),交由子類實現
acquire
獲取釋放分爲兩個模式:
- 獨佔模式
- 共享模式:在acquire獲取成功後,如果下一個等待節點是共享模式的,則會喚醒該節點(正是該特性,讓共享模式下,能同時獲取成功)
都是通過CAS的方式保證併發安全(已進入等待隊列的節點,按照先進先出的規則執行)。
獲取的流程:
- 調用try方法嘗試獲取,獲取成功直接返回,否則進入步驟
- 以指定模式(獨佔 or 共享)創建節點並添加到AQS隊列中
- 判斷當前節點是否爲第一個等待節點,如果不是進入步驟4,如果是則把head指向該節點(setHead方法,相當於該節點已經出隊了,共享模式此時會把後續共享模式的節點喚醒),然後返回interrupted(默認爲false,會在步驟5中被修改),進入到步驟6
- 調用shouldParkAfterFailedAcquire,判斷前面前面的等待節點是否有效,如果前面的節點已被取消先清理前面的節點再到步驟3,如果前面的節點狀態不是等待喚醒則更新狀態再到步驟3,如果前面的節點正常則掛起當前線程,到步驟5
- 如果步驟4中掛起了線程,從掛起中恢復,判斷是被中斷還是通過unpark導致的喚醒(parkAndCheckInterrupt),如果是中斷導致的把interrupted置爲true(會影響到後續是否掛起線程),進入步驟3
- 如果interrupted爲true(表明在等待的時候,被別的線程中斷了),掛起該線程
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
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);
}
}
值得注意的是,步驟5中爲什麼從中斷恢復後,還要判斷被喚醒還是中斷導致的恢復?開始看的時候看得很迷茫,爲什麼要這麼判斷,後面看了LockSupport.park方法,裏面註釋提到了,對該線程的LockSupport.unpark或者Thread.interrupt方法都會導致線程中掛起中恢復
release
釋放的流程:
- 調用try方法嘗試釋放,釋放成功進入下一步驟,否則直接返回
- 把head節點狀態置爲0,從等待隊列中取出第一個未被取消的等待節點,並喚醒該節點
獨佔模式的釋放代碼:
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);
}
基於AQS的同步類
目前java中基於AQS的有以下幾個:
- ReentrantLock
- ReentrantReadWriteLock
- Semaphore
- CountDownLatch
- ThreadPoolExecutor
Lock:ReentrantLock
ReentrantLock在構建的時候,可以以兩種模式創建,主要是在tryAcquire中是否需要判斷前面有沒有等待者:
- 公平模式:先進先出,先等待的會先去到鎖 非公平模式:不保證先等待的會先得到鎖,鎖剛釋放並此時有獲取鎖的線程會先得到鎖,減少了線程喚醒的開銷
公平模式的tryAcquire:
- 判斷state是否爲0,如果是並且是第一個等待者,則獲取成
- 如果不是第一個等待者,再判斷該線程是否與擁有鎖的線程爲同一線程,如果是則state屬性加1(acquire),獲取成功(可重入鎖)
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
非公平模式下的獲取:
- 判斷state爲0,如果是則直接獲取鎖成功
- 與公平模式相同
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
Lock:ReentrantReadWriteLock
ReentrantReadWriteLock也可以用公平、非公平兩種模式創建
同一個線程可同時擁有讀、寫鎖
總結:
獲取讀鎖時與寫鎖關係:(回顧下上面AQS的acquire中,共享模式下獲取成功,只要下一個也是共享模式的節點,會繼續喚醒該節點)
- 當前有本線程的寫鎖,獲取成功
- 當前有非本線程的寫鎖,獲取失敗,添加到AQS等待隊列
- 公平模式下,當前無寫鎖,前面有等待者,進入等待隊列,沒有等待者則獲取成功
- 非公平模式下,如果當前第一個等待者在等待寫鎖,則進入等待隊列(直到該節點前面所有的寫節點執行完成,就輪到該節點執行),否則獲取成功
獲取寫鎖時與讀鎖關係:
- 當前有讀鎖,添加到AQS等待隊列
- 當前有寫鎖,如果不是本線程的,添加到AQS等待隊列;如果是本線程的,獲取成功
- 當前沒有讀鎖、寫鎖,獲取成功
ReentrantReadWriteLock包含了兩個鎖:讀鎖和寫鎖,並且是用的同一個同步器,從上面AQS可知,AQS內部只有一個state屬性來表示鎖,因此這裏把int型的state分成兩半使用,前16位用來控制寫鎖的數量,後16位作爲讀鎖的數量
/** Inner class providing readlock */
private final ReentrantReadWriteLock.ReadLock readerLock;
/** Inner class providing writelock */
private final ReentrantReadWriteLock.WriteLock writerLock;
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
}
static final int SHARED_SHIFT = 16;
static final int SHARED_UNIT = (1 << SHARED_SHIFT);
static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1;
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
/** Returns the number of shared holds represented in count */
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
/** Returns the number of exclusive holds represented in count */
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
寫鎖
寫鎖是獨佔鎖,
獲取流程:
- 判斷當前是否有讀寫線程,如果有進入步驟2,沒有則到步驟4
- 如果有讀線程或者有非自己的寫線程,返回false,否則進入步驟3
- 如果已經達到最後數量,拋出異常,否則更新state狀態,返回ture(獲取成功)
- 如果是公平模式,需要判斷前面是否還有等待寫的線程,有則返回false,沒有則更新狀態、更新當前獨佔線程,返回true
protected final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread();
int c = getState();
int w = exclusiveCount(c);
if (c != 0) {
// (Note: if c != 0 and w == 0 then shared count != 0)
if (w == 0 || current != getExclusiveOwnerThread())
return false;
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// Reentrant acquire
setState(c + acquires);
return true;
}
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
setExclusiveOwnerThread(current);
return true;
}
釋放流程比較簡單,判斷是否爲擁有者,然後更新狀態、獨佔線程即可
讀鎖
從下面流程基本可以看到,讓讀線程堵塞等待的原因有三個
- 有非當前線程的寫線程
- 讀線程數已滿(65535)
- 公平模式下,前面有等待者;非公平模式下,第一個等待者爲寫線程
readHolds是ThreadLocal類型,用於記錄該線程獲取了鎖的數量
獲取流程:
- 判斷當前是否有非自己的寫線程,如果有則直接返回false,沒有則到步驟2
- 如果是公平模式,則判斷前面是否有等待者;如果是非公平模式,則判斷當前第一個等待者是否爲寫線程(如果等一個等待者是讀線程,則代表可以直接獲取讀鎖,不用等待,因爲是非公平);如果滿足條件,並且沒有超過讀的最大數量,則進入步驟3,不滿足進入步驟4
- 更新state的值,成功後根據情況更新firstReader 、firstReaderHoldCount、cachedHoldCounter(最後一次獲取成功讀線程信息),返回1(獲取成功)
- 調用fullTryAcquireShared方法重試(重新跑了一遍1-3的不步驟),結束後退出
對步驟4還是比較疑惑,他差不多就是1-3步驟的重複,根據他的解釋說也是一個重試步驟,但是爲什麼需要這個重試不大懂
protected final int tryAcquireShared(int unused) {
Thread current = Thread.currentThread();
int c = getState();
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
int r = sharedCount(c);
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {
if (r == 0) {
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
firstReaderHoldCount++;
} else {
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
}
return 1;
}
return fullTryAcquireShared(current);
}
final int fullTryAcquireShared(Thread current) {
/*
* This code is in part redundant with that in
* tryAcquireShared but is simpler overall by not
* complicating tryAcquireShared with interactions between
* retries and lazily reading hold counts.
*/
HoldCounter rh = null;
for (;;) {
int c = getState();
if (exclusiveCount(c) != 0) {
if (getExclusiveOwnerThread() != current)
return -1;
// else we hold the exclusive lock; blocking here
// would cause deadlock.
} else if (readerShouldBlock()) {
// Make sure we're not acquiring read lock reentrantly
if (firstReader == current) {
// assert firstReaderHoldCount > 0;
} else {
if (rh == null) {
rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current)) {
rh = readHolds.get();
if (rh.count == 0)
readHolds.remove();
}
}
if (rh.count == 0)
return -1;
}
}
if (sharedCount(c) == MAX_COUNT)
throw new Error("Maximum lock count exceeded");
if (compareAndSetState(c, c + SHARED_UNIT)) {
if (sharedCount(c) == 0) {
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
firstReaderHoldCount++;
} else {
if (rh == null)
rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
cachedHoldCounter = rh; // cache for release
}
return 1;
}
}
}
釋放流程,基本就是根據情況更新firstReader、firstReaderHoldCount信息,並且根據readHolds中判斷本線程是否已經不再擁有任何讀鎖,如果沒有了,從readHolds中去掉,最後更新state信息
Semaphore
整體邏輯比上面對的可重入鎖和讀寫鎖都簡單了很多,在初始化的時候,可以指定state的值,即可以被無釋放的acquire多少次,並且信號量不是可重入的,因此也不用判斷是否該線程已獲取
與讀寫鎖中的讀鎖一樣,是以共享模式進行獲取釋放
信號量也支持指定公平\非公平模式,因此在公平模式下還需要判斷是否有前面的等待者
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
protected int tryAcquireShared(int acquires) {
for (;;) {
if (hasQueuedPredecessors())
return -1;
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
protected final boolean tryReleaseShared(int releases) {
for (;;) {
int current = getState();
int next = current + releases;
if (next < current) // overflow
throw new Error("Maximum permit count exceeded");
if (compareAndSetState(current, next))
return true;
}
}
CountDownLatch
這個跟上面3有點不一樣,就是上面3類似於通行證,獲取了才能執行,而這邊是釋放了才能執行
創建的時候,會把state初始化一個指定值,await相當於等待state變爲0,而countDown就是給state減1
而還有個注意的是await提供了兩個版本:
- public void await() throws InterruptedException:等待過程中被中斷了,會拋出異常
- public boolean await(long timeout, TimeUnit unit) throws InterruptedException:在上面的基礎上,增加了可以指定等待的時長
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
public void countDown() {
sync.releaseShared(1);
}
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
ThreadPoolExecutor
線程池中的Worker繼承於AbstractQueuedSynchronizer,是一個不可重入鎖
而爲什麼Worker要用到AQS?
ThreadPoolExecutor在執行shutdown命令的時候,這個命令是會允許當前正在執行的任務完成,因此通過AQS來防止競爭,不可重入也是爲了防止此時tryLock成功
Worker的tryAcquyre和tryRelease方法:
protected boolean tryAcquire(int unused) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
protected boolean tryRelease(int unused) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
shutdown:
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(SHUTDOWN);
interruptIdleWorkers();
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
tryTerminate();
}
private void interruptIdleWorkers() {
interruptIdleWorkers(false);
}
private void interruptIdleWorkers(boolean onlyOne) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers) {
Thread t = w.thread;
if (!t.isInterrupted() && w.tryLock()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
} finally {
w.unlock();
}
}
if (onlyOne)
break;
}
} finally {
mainLock.unlock();
}
}