簡單的瞭解一下AQS吧

寧願。發表於2019-04-17

什麼是AQS

AQS,即AbstractQueuedSynchronizer,是一套定義了多執行緒訪問共享資源的同步器框架。在JDK的併發包中很多類都是基於AQS進行實現的,比如ReentrantLockCountDownLatch等。

AQS中的設計模式

如果單單只是看AQS類中的程式碼的haul可能會產生很多疑惑,因為類中很多方法都是隻有方法體,具體的實現需要到子類中才能看到。

模板方法模式

在我們平常的開發中會經常遇到一個問題,當我們接到一個需求時,在整理大體思路時會很清晰。但是當實際實現的時候會發現問題很多,有些步驟實現是沒有辦法確定下來的。會根據不同的需求進行更改。

這種邏輯流程確定,但是具體實現可能不同的問題可以通過模板方法模式來解決。

所謂的模板方法模式就是定義一個操作的流程骨架,確定呼叫流程。但是具體的實現則交給子類去完成。模板方法模式就是利用了物件導向中的多型特性。

在模板方法模式中有兩個重要的角色,一個是抽象模板類,另一個就是具體的實現類。

抽象模板類

抽象模板類用於定義業務流程,在該類中定義了一系列完成業務所需的方法。能夠確定的方法可以在抽象類中實現邏輯。不能確定的只是定義好方法,具體的實現由子類完成。

以AQS舉例,AbstractQueuedSynchronizer被定義為抽象類,其中一部分方法只是定義了方法體:

    protected boolean tryAcquire(int arg) {
        throw new UnsupportedOperationException();
    }
    
    protected boolean tryRelease(int arg) {
        throw new UnsupportedOperationException();
    }
複製程式碼

儘管這部分方法並沒有提供具體的實現,但是AQS中的其他方法還是直接呼叫了該方法。

    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
    public final void acquireInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        if (!tryAcquire(arg))
            doAcquireInterruptibly(arg);
    }
複製程式碼

跟句抽象類的特性,如果要使用這些方法的話就必須在子類繼承AQS並實現這些抽象方法。這樣的方法類被稱為模板類。

實現類

模板類的抽象方法的邏輯實現是在子類中完成的,不同的子類可以根據具體的需求進行個性化的實現。

比如ReentrantLock中Sync和FairSync對於tryAcquire的實現:

Sync:

    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;
    }
複製程式碼

FairSync:

    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;
    }
複製程式碼

這樣的類被稱為實現類。

AQS中的模板方法

AQS就是典型的應用模板方法模式的例子,如果我們要通過AQS來實現一個同步類。那麼我們需要實現以下方法:

tryAcquire(int) 
tryRelease(int) 
tryAcquireShared(int) 
tryReleaseShared(int) 
isHeldExclusively() 
複製程式碼

部分引數解析

state

state引數是非常重要的一個引數,AQS的鎖狀態就是依賴於改引數實現的。

AQS中對鎖的操作是利用CAS進行實現,而cas主要操作的物件就是state引數。當state=0時表示可以獲取鎖,而當state!=0時則表示已經進行了加鎖操作。

可重入鎖的實現也依賴於該引數,當持有鎖的執行緒再次獲取一次鎖時便將state的值加一,而每一次釋放一次鎖則進行減一操作,只有當state=0時才算是釋放鎖完畢。

Node

static final class Node {

        static final Node SHARED = new Node();

        static final Node EXCLUSIVE = null;

        static final int CANCELLED =  1;

        static final int SIGNAL    = -1;

        static final int CONDITION = -2;

        static final int PROPAGATE = -3;

        volatile int waitStatus;

        volatile Node prev;

        volatile Node next;

        volatile Thread thread;

        Node nextWaiter;
}
複製程式碼

Node用於儲存獲取鎖失敗時

Node.SHARED和Node.EXCLUSIVE

在AQS的具體實現中存在兩種不同模式的鎖:排他鎖和共享鎖

一般共享鎖主要用於讀操作,表示讀操作可以是多個執行緒同時進行,而不會阻塞;排他鎖主要用於寫操作,會進行阻塞

而排他鎖和共享鎖的實現就依賴於Node.SHARED和Node.EXCLUSIVE區分。比如ReentrantReadWriteLock

waitStatus

waitStatus用於表示當前節點所處的狀態。

  • 初始狀態:節點初始狀態值被初始化為0,如果是通過condition註冊的節點其初始狀態為-2(CONDITION)
  • CANCELLED:static final int CANCELLED = 1;由於超時或者中斷等原因使得當前節點被標記位取消狀態。一般來說被標記為取消狀態的節點不會再去競爭鎖並且不能轉換為其他狀態。
  • SIGNAL:static final int SIGNAL = -1;當前節點的後繼節點通過park被阻塞(或者將要被阻塞)。那麼在當前節點釋放或者取消時需要通過unpark取消阻塞。
  • CONDITION:static final int CONDITION = -2;將節點放在condition佇列中是需要標識其狀態為CONDITION。
  • PROPAGATE:static final int PROPAGATE = -3;該狀態值用於在共享狀態下,當共享狀態的鎖被釋放後,該操作會被傳播到其他節點。

prev next

prev和next分別用於記錄前驅節點和後繼節點

重要方法解析

tryAcquire

protected boolean tryAcquire(int arg);

tryAcquire字面意思很明確,就是嘗試獲取鎖。獲取鎖成功則返回true,獲取鎖失敗則將該執行緒放入等待佇列中,等待佔用資源的執行緒被釋放。

在JDK中明確定義tryAcquire方法用於獲取的處於獨佔模式下的鎖。如果不是獨佔模式則丟擲異常UnsupportedOperationException

該方法需要被重寫。

該方法共享模式版本為protected int tryAcquireShared(int arg).

tryRelease

protected boolean tryRelease(int arg);

該方法用於在獨佔模式下通過cas嘗試設定state狀態值,用於釋放鎖操作。

修改值成功則返回true。如果不是獨佔模式則丟擲異常UnsupportedOperationException

該方法需要被重寫。

該方法的共享模式方法為protected boolean tryReleaseShared(int arg)

isHeldExclusively

該方法用於來判斷是否當前執行緒正在以獨佔模式進行同步操作。

setState和compareAndSetState

setState和compareAndSetState兩個方法都是對state引數的值進行設定。

不同之處在於compareAndSetState主要用於獲取鎖時修改狀態值,因為獲取鎖時存在競爭問題所以需要原子操作獲取。

而setState操作用於在釋放鎖是修改state的值,釋放鎖時只有持有鎖的執行緒會進行釋放,不存在競爭問題,不需要原子操作。

動手實現一個同步類

現在我們來實現一個我們自己的同步類,一個不可重入的獨佔鎖。

public class MyLock implements Lock {

    static class Sync extends AbstractQueuedSynchronizer{
        @Override
        protected boolean tryAcquire(int arg) {
            //這裡只有當state=0時才能獲取鎖 表示該同步類不可重入
            if(compareAndSetState(0,1)){
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        @Override
        protected boolean tryRelease(int arg) {
            if(getState()!=1){
                //無法再被釋放
                throw new IllegalMonitorStateException();
            }
            setState(0);
            setExclusiveOwnerThread(null);
            return true;
        }

        @Override
        protected boolean isHeldExclusively() {
            return getState()==1 || 
                    getExclusiveOwnerThread()==Thread.currentThread();
        }

        // 返回一個Condition,每個condition都包含了一個condition佇列
        Condition newCondition() {
            return new ConditionObject();
        }
    }

    private final Sync sync = new Sync();
    
    @Override
    public void lock() {
        sync.acquire(1);
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

    @Override
    public boolean tryLock() {
        return sync.tryAcquire(1);
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return sync.tryAcquireNanos(1,unit.toNanos(time));
    }

    @Override
    public void unlock() {
        sync.release(0);
    }

    @Override
    public Condition newCondition() {
        return sync.newCondition();
    }
}

複製程式碼

即使我們並不知道AQS的內部實現,只需要瞭解AQS中的幾個方法作用並在子類中重寫這些方法就能設計出一個簡單的同步類。

相關文章