每日一面——談談你對AQS的理解

菜雞每日一面系列打卡21

每天一道面試題目 

助力小夥伴輕鬆拿offer

堅持就是勝利,我們一起努力!

題目描述

談談你對AQS的理解。

題目分析

在之前的文章中我們提到過,ReentrantLock等一系列JDK所提供的同步器都是基於AQS構建的,因此可以說,不瞭解AQS,就不算真正掌握了同步器。而有關AQS的底層原理的考查,也是各個大廠面試過程中的熱點問題。

本文將從源碼級別深入分析AQS,並以ReentrantLock爲例,介紹如何使用AQS構建同步器。不瞭解ReentrantLock的小夥伴,可以先移步文末的相關鏈接進行學習。菜雞希望,本篇文章能使小夥伴們徹底搞懂AQS以及模板方法設計模式,從而可以隨心所欲地構建自定義同步器,那麼,面試自然就不在話下了。

接下來,隨菜雞一起去看看吧。

題目解答

01

什麼是AQS

AQSAbstractQueuedSynchronizer類的簡稱,從名字我們可以猜測,AQS是一個虛擬類,基於隊列實現,用於構建同步器。這個猜測是正確的,它是JDK提供的一個基於FIFO等待隊列實現阻塞鎖及相關同步器(Semaphore等)的一個框架,它是大多數同步器的基礎,用一個volatileint類型變量state來表示狀態。


AQS的核心設計思想是,如果當前線程請求的共享資源處於閒置狀態,則將該線程設置爲工作線程,並將該資源設置爲鎖定狀態。如果該資源處於鎖定狀態,則將該線程加入CLH隊列中。

那麼問題來了!什麼是CLH隊列?無需解釋,看完下圖就全明白了。

02

源碼分析

上文談到了AQS是用一個volatileint類型變量state來表示狀態。接下來我們看一下源碼。

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {


    ......


    private volatile int state;


    protected final int getState() {
        return state;
    }


    protected final void setState(int newState) {
        state = newState;
    }
    
    protected final boolean compareAndSetState(int expect, int update) {
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }


    ......


}

AQS定義了兩種資源共享方式,獨佔鎖與共享鎖。所謂的獨佔鎖就是,只有一個線程可以執行,比如之前談過的ReentrantLock就是獨佔鎖的一種,而共享鎖,就是多個線程可以執行,比如上文提到的Semaphore。在此就不展開。

ReentrantLock爲例,我們可以學習基於AQS的公平鎖與非公平鎖的實現。我們看一下ReentrantLock類中相關部分的源碼,首先,我們看一下它的構造方法。

public class ReentrantLock implements Lock, java.io.Serializable {
    
    ......
    
    private final Sync sync;
    
    ......
    
    public ReentrantLock() {
        sync = new NonfairSync();
    }


    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }
    
    ......
    
}

如源碼所示,ReentrantLock的默認構造函數是非公平鎖。接下來,我們看一下NonfairSync類與FairSync類的不同之處。首先,我們看一下公平鎖的實現。

public class ReentrantLock implements Lock, java.io.Serializable {


    ......
    
    // 非公平鎖
    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;
        
        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }


        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }


    // 公平鎖
    static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;


        final void lock() {
            acquire(1);
        }


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

可以看出,非公平鎖和公平鎖主要有以下不同:

  • 二者的lock方法不同,非公平鎖在調用lock方法後,會CAS搶佔鎖,如果鎖沒有被佔用,則直接獲取。

  • 非公平鎖在CAS失敗後,會同公平鎖一樣進入到tryAcquire方法,如果發現鎖被釋放(state == 0),非公平鎖會CAS搶佔鎖,而公平鎖則會檢查等待隊列是否有處於等待狀態的線程。

03

模板方法模式

同步器的設計是基於模板方法模式的,從ReentrantLock的源碼我們也可以看出,自定義同步器方式大致如下:

  • 使用者繼承AQS並重寫指定的方法。

  • AQS以組合的形式引入自定義同步組件的實現中,並調用其模板方法,模板方法會調用重寫的方法。

  • 具體需要重寫的方法可以參考ReentrantLock的源碼,在此便不再贅述。

以上便是菜雞對AQS的一些總結,供大家參考。

相關鏈接

每日一面——談談你對ReentrantLock的理解

學習 | 工作 | 分享

????長按關注“有理想的菜雞

只有你想不到,沒有你學不到

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