JAVA多線程第三部分(二) AQS

併發筆記傳送門:
1.0 併發編程-思維導圖
2.0 併發編程-線程安全基礎
3.0 併發編程-基礎構建模塊
4.0 併發編程-任務執行-Future
5.0 併發編程-多線程的性能與可伸縮性
6.0 併發編程-顯式鎖與synchronized
7.0 併發編程-AbstractQueuedSynchronizer
8.0 併發編程-原子變量和非阻塞同步機制

ReentrantLockSemaphore這兩個接口之間存在許多共同點。這兩個類都可以用做一個“閥門”,即每次只允許一定數量的線程通過,並當線程到達閥門時,可以通過(在調用lockacquire時成功返回),也可以等待(在調用lockacquire時阻塞),還可以取消(在調用tryLocktryAcquire時返回“假”,表示在指定的時間內鎖是不可用的或者無法獲得許可)。而且,這兩個接口都支持可中斷的、不可中斷的以及限時的獲取操作,並且也都支持等待線程執行公平或非公平的隊列操作。

事實上,它們在實現時都使用了一個共同的基類,即AbstractQueuedSynchronizer(AQS),這個類也是其他許多同步類的基類。AQS是一個用於構建鎖和同步器的框架,許多同步器都可以通過AQS很容易並且高效地構造出來。不僅ReentrantLockSemaphore是基於AQS構建的,還包括CountDownLatchReentrantReadWriteLockSynchronousQueueFutureTask

AQS 中的獲取與釋放

在基於 AQS 構建的同步器類中,最基本的操作包括各種形式的鎖獲取和釋放操作。並且獲取操作是一種依賴狀態的操作,並且通常會阻塞。
如下僞代碼給出了 AQS 獲取與釋放的簡單邏輯。 (Douge Lea 老爺子源碼寫的太精妙,得慢慢品)

    boolean acquire() throws InterruptedException{
        while (當前狀態不允許獲取操作){
            if (需要阻塞獲取請求){
                如果當前線程不再隊列中,則將其插入隊列
                阻塞當前線程
            }else {
                返回失敗
            }
        }
        更新同步器的狀態
        如果線程位於隊列中,則將其移除隊列
        返回成功
    }
    void release(){
        更新同步器的狀態
        if(新的狀態允許某個阻塞的線程獲取成功){
            解除隊列中一個或多個線程的阻塞狀態
        }
    }

一個獲取操作包括兩部分:

  • 首先,同步器判斷當前狀態是否允許獲得操作,如果是,則允許線程執行,否則獲取操作將阻塞或失敗。這種判斷是根據同步器的語義決定的。例如:對於鎖來說,如果它沒有被某個線程持有,那麼就能成功的獲取;而對於閉鎖來說,如果它處於結束狀態,那麼也能被成功的獲取。
  • 其次,就是更新同步器的狀態,獲取同步器的某個線程可能會對其他線程能否獲取該同步器照成影響。例如,當獲取一個鎖後,鎖的狀態將『未備持有』變成『已被持有』,而從Semaphore中獲取許可後,將把許可證的數量減1。然而,當一個線程獲取閉鎖時,並不會影響其他線程能否獲取它。

根據同步器性質的不同,實現的方法各有差異:

  • 獨佔操作(例如:ReentrantLock):如果某個同步器支持獨佔的獲取操作,那麼需要實現 AQStryAcquiretryReleasetryHeldExeclusively等方法。
  • 非獨佔操作(例如:Semphore,CountDownLatch):對於支持共享獲取的同步器,則應該實現tryAcquireSharedtryReleaseShared等方法

AQS 中的的acquireacquireSharedreleasereleaseShared等方法都將調用這些方法在子類中帶有前綴try的版本來判斷某個操作是否能夠執行。

一個簡單的閉鎖

OneShotLatch包含兩個公有方法:awaitsignal,分別對應獲取和釋放操作。起初,閉鎖是關閉的,任何調用 await 的線程都將阻塞並直到閉鎖打開。當通過調用 signal 打開閉鎖時,所有等待中的線程豆漿被釋放,並且隨後到達閉鎖的線程也允許被執行。

import java.util.concurrent.locks.AbstractQueuedSynchronizer;

public class OneShotLatch {
    private final Sync sync = new Sync();

    public void signal() {
        sync.releaseShared(0);
    }

    public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(0);
    }

    private static class Sync extends AbstractQueuedSynchronizer {
        @Override
        protected int tryAcquireShared(int arg) {
            int state = getState();
            //如果閉鎖是開的(state==1),那麼這個操作講成功,否則失敗
            System.out.println("state = " + state);
            return getState() == 1 ? 1 : -1;
        }

        @Override
        protected boolean tryReleaseShared(int arg) {
            setState(1);//打開閉鎖
            return true;//其他線程可以獲取該閉鎖
        }
    }

    public static void main(String[] args) {
        OneShotLatch osl = new OneShotLatch();
        new Thread(() -> {
            System.out.println("we are in main 01 thread, and start osl await");
            try {
                osl.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("main 01 thread osl await finished");
        }).start();
        new Thread(() -> {
            System.out.println("we are in main 02 thread, and start osl await");
            try {
                osl.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("main 02 thread osl await finished");
        }).start();
        new Thread(() -> {

            System.out.println("we are in main 03 thread, and first sleep 5s");
            try {
                Thread.sleep(5000);
                System.out.println("we are in main 03 thread, and start osl await");
                osl.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("main 03 thread osl await finished");
        }).start();
        new Thread(() -> {
            System.out.println("we are in work thread,and we start waiting");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("work finish,now we signal main thread");
            osl.signal();
        }).start();
    }
}

java.util.concurrent中的 AQS

併發包中有許多的可阻塞類,例如ReentrantLockSemaphoreCountDownLatchReentrantReadWriteLockSynchronousQueueFutureTask等,都是基於 AQS 構建的。

ReentrantLock

ReentrantLock只支持獨佔方式的獲取操作,因此它實現了 tryAcquiretryReleaseisHeldExclusively方法。

  • ReentrantLock將同步狀態state用於保存鎖獲取操作的次數。
  • 維護了一個 owner 變量來保存當前線程,但是在1.6上進行了重構增加了AbstractOwnableSynchronizerexclusiveOwnerThread來保存當前線程。只有在當前線程剛剛獲取到鎖,或者正要釋放鎖的時候,纔會修改這個變量。
    • tryRelease 中檢查 owner 域,從而確保當前線程在執行 unlock 操作前已經獲取了鎖
    • tryAcquire 中將使用 owner 域判斷獲取操作是重入還是競爭的

非公平鎖版本(默認)

    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;

        /**
         * Performs lock.  Try immediate barge, backing up to normal
         * acquire on failure.
         */
        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

        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;
        }

當一個線程嘗試獲取鎖時,tryAcquire 將首先檢查鎖的狀態:

  • 未被持有:通過 compareAndSetState(0, acquires)原子性的操作嘗試更新鎖的狀態以表示已經被持有。
  • 已經持有:判斷當前現場是否爲鎖的擁有者,是:計數遞增(所以 ReentrantLock 是可重入鎖);不是:獲取操作失敗。

SemaphoreCountDownLatch

SemaphoreCountDownLatch是屬於支持共享獲取的同步器,因此它們實現了 tryAcquireSharedtryReleaseShared 方法

Semaphorestate 用於保存當前可用許可數量。

Semaphore的非公平鎖實現爲例:

    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = -2694183684443567898L;

        NonfairSync(int permits) {
            super(permits);
        }

        protected int tryAcquireShared(int acquires) {
            return nonfairTryAcquireShared(acquires);
        }
    }
        final int nonfairTryAcquireShared(int acquires) {
            for (;;) {
                int available = getState();
                int remaining = available - acquires;
                //這個代碼很精髓啊
                //remaining < 0 :如果沒有足夠的許可,退出循環
                //compareAndSetState設置成功,退出循環;設置失敗,重新嘗試
                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;
            }
        }

tryAcquireShared首先會計算剩餘許可的數量。

  • 如果數量不足,那麼會返回一個值表示獲取操作失敗。
  • 如果還有剩餘的許可數量,會通過compareAndSetState以原子的方式來降低許可的計數。如果這個操作成功(意味着從上次讀取後就沒有被修改過),那麼就返回一個值表示操作獲取成功。

CountDownLatch使用 AQS 的方式與Semaphore很相似:同步狀態 state用來保存當前的計數值
await() 調用關係:

graph LR
await-->acquireSharedInterruptibly 
acquireSharedInterruptibly --> tryReleaseShared

await 調用 acquire,當計數器爲0時,acquire 將立即返回,否則將執行doAcquireSharedInterruptibly進入阻塞。

    private static final class Sync extends AbstractQueuedSynchronizer {
        Sync(int count) {
            setState(count);
        }
        int getCount() {
            return getState();
        }
        protected int tryAcquireShared(int acquires) {
            return (getState() == 0) ? 1 : -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;
            }
        }
    }
    public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        if (tryAcquireShared(arg) < 0)
            doAcquireSharedInterruptibly(arg);
    }
    public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }

countDown()調用關係:

graph LR
countDown-->releaseShared
releaseShared-->tryReleaseShared

countDown() 調用 tryReleaseShared來完成計數遞減,當計數值爲0時,執行doReleaseShared解除所有等待線程的阻塞。

    public void countDown() {
        sync.releaseShared(1);
    }
    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }

FutureTask

老爺子新版本並沒有直接使用 AQS,通過sun.misc.Unsafe UNSAFE 來操作實現。

ReentrantReadWriteLock

ReadWriteLock接口表示存在兩個鎖:讀取鎖和寫入鎖,但在基於 AQS 實現的 ReentrantReadWriteLock 中,單個AQS子類將同時管理讀取加鎖和寫入加鎖。

ReentrantReadWriteLock使用了兩個16位的狀態分別表示寫入鎖和讀取鎖的計數。在讀取鎖上的操作使用共享的獲取、釋放方式;在寫入鎖上的操作使用獨佔的獲取、釋放方式。
AQS 在內部維護一個等待線程隊列,其中記錄了某個線程是獨佔(Node.EXCLUSIVE)還是共享(Node.SHARE)訪問。

小結

AQS 源碼真心複雜,本篇只是粗淺的記錄下併發包內的 AQS 的使用情況,下一篇爭取啃下 AQS 的實現原理

Doug Lea老爺子一個人擼起了 java 併發的大旗,真滴猛。

以下是是網上比較好的 AQS 源碼解析,記錄一下

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