同步類的基礎AbstractQueuedSynchronizer(AQS)
我們之前介紹了很多同步類,比如ReentrantLock,Semaphore, CountDownLatch, ReentrantReadWriteLock,FutureTask等。
AQS封裝了實現同步器時設計的大量細節問題。他提供了FIFO的wait queues並且提供了一個int型的state表示當前的狀態。
根據JDK的說明,並不推薦我們直接使用AQS,我們通常需要構建一個內部類來繼承AQS並按照需要重寫下面幾個方法:
- tryAcquire
- tryRelease
- tryAcquireShared
- tryReleaseShared
- isHeldExclusively
在這些方法中,我們需要調用getState, setState 或者 compareAndSetState這三種方法來改變state值。
上面的方法提到了兩種操作,獨佔操作(如:ReentrantLock)和共享操作(如:Semaphore,CountdownLatch)。
兩種的區別在於同一時刻能否有多個線程同時獲取到同步狀態。
比如我們運行同時多個線程去讀,但是通知只允許一個線程去寫,那麼這裏的讀鎖就是共享操作,而寫鎖就是獨佔操作。
在基於QAS構建的同步類中,最基本的操作就是獲取操作和釋放操作。而這個state就表示的是這些獲取和釋放操作所依賴的值。
State是一個int值,你可以使用它來表示任何狀態,比如ReentrantLock用它來表示所有者線程重複獲取該鎖的次數。Semaphore用它來表示剩餘的許可量,而FutureTask用它來表示任務的狀態(開始,運行,完成或者取消)。當然你還可以自定義額外的狀態變量來表示其他的信息。
下的僞代碼表示的是AQS中獲取和釋放操作的形式:
Acquire:
while (!tryAcquire(arg)) {
enqueue thread if it is not already queued;
possibly block current thread;
}
Release:
if (tryRelease(arg))
unblock the first queued thread;
獲取操作,首先判斷當前狀態是否允許獲取操作,如果如果不允許,則將當前的線程入Queue,並且有可能阻塞當前線程。
釋放操作,則先判斷是否運行釋放操作,如果允許,則解除queue中的thread,並運行。
我們看一個具體的實現:
public class AQSUsage {
private final Sync sync= new Sync();
private class Sync extends AbstractQueuedSynchronizer{
protected int tryAcquireShared(int ignored){
return (getState() ==1 )? 1: -1;
}
protected boolean tryReleaseShared(int ignored){
setState(1);
return true;
}
}
public void release() {
sync.releaseShared(0);
}
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(0);
}
}
上面的例子中,我們定義了一個內部類Sync,在這個類中我們實現了tryAcquireShared和tryReleaseShared兩個方法,在這兩個方法中我們判斷並設置了state的值。
sync.releaseShared和sync.acquireSharedInterruptibly會分別調用tryAcquireShared和tryReleaseShared方法。
前面我們也提到了很多同步類都是使用AQS來實現的,我們可以再看看其他標準同步類中tryAcquire的實現。
首先看下ReentrantLock:
final boolean tryAcquire(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;
}
ReentrantLock只支持獨佔鎖。所以它需要實現tryAcquire方法。除此之外它還維護了一個owner變量來保存當前所有者線程的標誌符,從而來實現可重入鎖。
我們再看下Semaphore和CountDownLatch的實現,因爲他們是共享操作,所以需要實現tryAcqureShared方法:
final int tryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
本文的例子請參考https://github.com/ddean2009/learn-java-concurrency/tree/master/AQS
更多內容請訪問 flydean的博客