開發與維運

AQS

@[toc]

1.學習AQS的思路

  • 瞭解原理,提高思路
  • 先了解如何使用,應用場景,再去分析它的結構

2.為什麼需要AQS

  • 鎖的協作類共同點:閘門 (和 ReentrantLock、Semaphore相似)
  • 不僅 ReentrantLock、Semaphore、像CountDownLatch、都有這樣的協作 (或者同步)功能,其實,他們底層都用了一個共同的基類,這就是 AQS
  • 因為這些協作類有很多工作都是類似的,所以如果提取出一個工具類,那麼就直接就可以用
  • Semaphore 、CountDownLatch 內部有一個類 Sync , Sync類繼承了AQS

在這裡插入圖片描述

3.AQS的作用

如果沒有AQS:
: 就需要每個協作工具直接實現
: 線程的阻塞與解除阻塞
: 隊列的管理

  • AQS是一個用於構建鎖、同步器、協作工具類的工具類(框架)。有了AQS以後,更多的協作工具類都可以很方便得被寫出來
  • 有了AQS,構建線程協作類就容易多了

4.AQS的重要性、地位

AbstractQueuedSynchronizer 是 Doug Lea 寫的,從JDK1.5加入的一個基於FIFO等待隊列實現的一個用於實現同步器的基礎框架,我沒用IDE看AQS的實現類,可以發現實現類有這些實現

在這裡插入圖片描述

5.AQS內部原理解析

AQS三大核心:

  • state
  • 控制線程搶鎖和配合的FIFO隊列
  • 期望協作工具類去實現的獲取/釋放等重要方法

State狀態

  • 這裡等state的具體含義,會根據具體實現類的不同而不同,比如在Semaphore 裡,它表示 “ 剩餘的許可證數量” ,而CountDawnLatch 裡,它表示 “ 還需要倒數的數量 ”
  • state 是 volatile修飾的,會被併發地修改,所以所有修改state的方法要保證線程安全,比如getState 、setState 以及 compareAandState 操作來讀取和更新這個狀態。這些方法都依賴與j.u.c.atomic包的支持
/**
     * The synchronization state.
     * 線程同步狀態 利用來volatile 保證可見性
     */
    private volatile int state;

 /**
     * Atomically sets synchronization state to the given updated
     * value if the current state value equals the expected value.
     * This operation has memory semantics of a {@code volatile} read
     * and write.
     * 
     * 利用了 unsafe 類的 cas操作
     * 
     * @param expect the expected value
     * @param update the new value
     * @return {@code true} if successful. False return indicates that the actual
     *         value was not equal to the expected value.
     */
    protected final boolean compareAndSetState(int expect, int update) {
        // See below for intrinsics setup to support this
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }

像在 ReentrantLock中

  • state用來表示 “鎖”的佔有情況,包括可重入鎖計數
  • 當state 的值為 0的時候,標識改Lock不被任何線程所佔有

FIFO隊列

這個隊列用來存放 “等待的線程”,AQS就是 “排對管理器”,當多個線程爭用同一把鎖的時候,必須有排隊機制講那些沒能拿到鎖的線程串在一起。當鎖釋放時,鎖管理器就會挑選一個合適的線程佔有這個剛剛釋放的鎖

獲取/釋放的方法

這裡的獲取和釋放方法,是利用AQS的協作工具類裡最重要的方法,是由協作類自己實現的,並且含義各不相同

獲取:

  • 獲取操作會依賴state變量,經常會阻塞(比如獲取不到鎖的時候)
  • 在Semaphore 中 ,獲取就是acquire 方法,作用就是獲取一個許可證
  • 而在CountDownLatch裡,獲取就是await方法,作用等待,直到倒數結束

釋放:

  • 釋放操作不會被阻塞
  • 在Semaphore中 ,釋放就是 release 方法,作用是釋放一個許可證
  • CountDownLatch 裡面,獲取就是countDown 方法,作用是 “倒數一個數”

6.應用實例,源碼解析

我們看下 CountDownLatch的源碼

  • 構造方法
    /**
     * 構造函數 , 傳入int值,判斷小於0 給Sync 賦值
     */
     public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }
/**
* Sync 繼承類AQS  Sync的構造獲
*/
private static final class Sync extends AbstractQueuedSynchronizer {
   

        Sync(int count) {
            setState(count);
        }

        /**
         *把AQS state值設置 newState
         */
       protected final void setState(int newState) {
        state = newState;
    }


/**
 * 調用如下 獲取AQS類的  
 * private volatile int state;
 */

 protected final int getState() {
        return state;
    }
  • getCount() 方法

// 調用AQS 的getState()方法
int getCount() {
            return getState();
 }

//AQS返回state的值
protected final int getState() {
        return state;
}
  • await() 方法
//進行等待 調用AQS    sync.acquireSharedInterruptibly(1);
 public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }
public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
            //如果小於0 則放入等待隊列中
        if (tryAcquireShared(arg) < 0)
            doAcquireSharedInterruptibly(arg);
    }
 private void doAcquireSharedInterruptibly(int arg)
        throws InterruptedException {
        //包裝成Node節點 
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
        //死循環
            for (;;) {
                final Node p = node.predecessor();
                if (p == head) {
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        failed = false;
                        return;
                    }
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                //中斷線程的方法
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
//park 調用的 LockSupport.park(this);
private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }
  • countDown() 方法
// 減一減一 直到0開始喚醒
 public void countDown() {
        sync.releaseShared(1);
    }


public final boolean releaseShared(int arg) {
        //判斷是否等於0 
        if (tryReleaseShared(arg)) {
        // 喚醒所有線程
            doReleaseShared();
            return true;
        }
        return false;
    }
  • 總結

調用CountDownLatch的await方法時,便會嘗試獲取 "共享鎖",不過一開始是獲取不到鎖的,於是線程阻塞/

而 “共享鎖” 可獲取到的條件,就是 “鎖計數器” 的值為0。

而 “鎖計數器初始值為count” 沒當一個線程調用 才減一

當count 線程調用countDown()之後 , “鎖計數器” 才為 0,而前面提到的等待獲取共享個i昂鎖的線程才能繼續運行

7.AQS優質學習資源

Leave a Reply

Your email address will not be published. Required fields are marked *