同步類的基礎AbstractQueuedSynchronizer(AQS)

同步類的基礎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的博客

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章