什么是 AQS?簡單說一下 ReentrantLock 的原理?


AQS 簡介

java的內置鎖一直都是備受爭議的,在JDK 1.6之前,synchronized這個重量級鎖其性能一直都是較為低下,雖然在1.6后,進行大量的鎖優化策略,但是與Lock相比synchronized還是存在一些缺陷的:雖然synchronized提供了便捷性的隱式獲取鎖釋放鎖機制(基於JVM機制),但是它卻缺少了獲取鎖與釋放鎖的可操作性,可中斷、超時獲取鎖,且它為獨占式在高並發場景下性能大打折扣。

在介紹Lock之前,我們需要先熟悉一個非常重要的組件,掌握了該組件JUC包下面很多問題都不在是問題了。該組件就是AQS。

AQS:AbstractQueuedSynchronizer,即隊列同步器。它是構建鎖或者其他同步組件的基礎框架(如ReentrantLock、ReentrantReadWriteLock、Semaphore等),JUC並發包的作者(Doug Lea)期望它能夠成為實現大部分同步需求的基礎。它是JUC並發包中的核心基礎組件。

AQS解決了實現同步器時涉及當的大量細節問題,例如獲取同步狀態、FIFO同步隊列。基於AQS來構建同步器可以帶來很多好處。它不僅能夠極大地減少實現工作,而且也不必處理在多個位置上發生的競爭問題。

在基於AQS構建的同步器中,只能在一個時刻發生阻塞,從而降低上下文切換的開銷,提高了吞吐量。同時在設計AQS時充分考慮了可伸縮行,因此J.U.C中所有基於AQS構建的同步器均可以獲得這個優勢。

AQS 原理簡介

AQS 內部簡單來說其實主要是由三部分組成的

  1. state 這個狀態用來聲明對象是否已經被線程占有,狀態為 0 表示沒有,state > 0則表示已經被其他線程占有

  2. 當前線程 聲明當前占有的線程

  3. 排隊隊列 等待占有該對象的線程

AQS 在 Java 中的應用

ReentrantLock、ReentrantReadWriteLock、Semaphore等很多 JUC的加鎖方式都是繼承自 AQS,比如:

    /**
     * Base of synchronization control for this lock. Subclassed
     * into fair and nonfair versions below. Uses AQS state to
     * represent the number of holds on the lock.
     */
    abstract static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = -5179523762034025860L;

        /**
         * Performs {@link Lock#lock}. The main reason for subclassing
         * is to allow fast path for nonfair version.
         */
        abstract void lock();

        /**
         * Performs non-fair tryLock.  tryAcquire is implemented in
         * subclasses, but both need nonfair try for trylock method.
         */
        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;
        }

        protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }

        protected final boolean isHeldExclusively() {
            // While we must in general read state before owner,
            // we don't need to do so to check if current thread is owner
            return getExclusiveOwnerThread() == Thread.currentThread();
        }

        final ConditionObject newCondition() {
            return new ConditionObject();
        }

        // Methods relayed from outer class

        final Thread getOwner() {
            return getState() == 0 ? null : getExclusiveOwnerThread();
        }

        final int getHoldCount() {
            return isHeldExclusively() ? getState() : 0;
        }

        final boolean isLocked() {
            return getState() != 0;
        }

        /**
         * Reconstitutes the instance from a stream (that is, deserializes it).
         */
        private void readObject(java.io.ObjectInputStream s)
            throws java.io.IOException, ClassNotFoundException {
            s.defaultReadObject();
            setState(0); // reset to unlocked state
        }
    }

結合 ReentrantLock 解釋 AQS

這里我們說簡單說一個業務場景,代碼如下:

				ReentrantLock reentrantLock = new ReentrantLock();
        reentrantLock.lock();
        // 一大堆代碼
        reentrantLock.unlock();
        // 一大堆代碼

現在有多個線程調用這個方法,但是這個里面有一個 map 是會被多個線程占用的。那么這個時候會發生什么呢?

線程 1 和線程 2 嘗試獲取加鎖

1、當線程1處理的時候,對個線程嘗試加鎖,然后通過 CAS 加鎖成功,將 AQS 的 state 修改為 1(默認值為 0),當前線程設置為線程 1

2、這個時候線程 2 開始處理,嘗試加鎖。CAS 的時候發現這個state 狀態已經不是 0 了,說明某個線程正在占用。加鎖失敗。

3、線程 2 進入 AQS 的排隊隊列,然后線程 2 掛起。

線程 1 邏輯執行完畢,釋放鎖。線程 2 嘗試獲取鎖

1、當線程 1 完成業務邏輯后,執行 unlock 操作的時候,會將 state 修改為 0,然后將當前線程變量修改為 null

2、喚醒排隊隊列中的第一個線程,讓其重新出嘗試加鎖

非公平鎖

1、Reentrantlock默認是非公平鎖。

2、為什么說他是非公平鎖呢,還是剛才線程 1 結束,喚醒線程 2 的場景。

3、如果在喚醒線程 2 之后,線程 2 加鎖成功之前,出現了線程 3,線程 3 直接進行CAS 加鎖,而且還成功了

4、這時候線程 2 就尷尬了,他發現自己被喚醒之后還是 CAS 加鎖失敗,他就會又回到隊列里面去重新等待被喚醒。

為什么說是非公平的,明明大家都在排隊,這個時候突然插隊進來一個哥們,這對排隊的人來說是不公平的

公平鎖

1、在初始化 Reentrantlock 的時候默認值給一個 true ,這個時候他就是公平鎖了。

2、還是剛才的場景,如果線程 2 被喚醒之后有一個新的線程,如線程 3 嘗試加鎖,那他會判斷隊列中是否有值。

3、如果沒有值,則進行CAS 加鎖嘗試

4、如果有值則插入隊列等待排隊喚醒


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM