菜雞每日一面系列打卡21天
每天一道面試題目
助力小夥伴輕鬆拿offer
堅持就是勝利,我們一起努力!
題目描述
談談你對AQS的理解。
題目分析
在之前的文章中我們提到過,ReentrantLock等一系列JDK所提供的同步器都是基於AQS構建的,因此可以說,不瞭解AQS,就不算真正掌握了同步器。而有關AQS的底層原理的考查,也是各個大廠面試過程中的熱點問題。
本文將從源碼級別深入分析AQS,並以ReentrantLock爲例,介紹如何使用AQS構建同步器。不瞭解ReentrantLock的小夥伴,可以先移步文末的相關鏈接進行學習。菜雞希望,本篇文章能使小夥伴們徹底搞懂AQS以及模板方法設計模式,從而可以隨心所欲地構建自定義同步器,那麼,面試自然就不在話下了。
接下來,隨菜雞一起去看看吧。
題目解答
01
什麼是AQS
AQS是AbstractQueuedSynchronizer類的簡稱,從名字我們可以猜測,AQS是一個虛擬類,基於隊列實現,用於構建同步器。這個猜測是正確的,它是JDK提供的一個基於FIFO等待隊列實現阻塞鎖及相關同步器(Semaphore等)的一個框架,它是大多數同步器的基礎,用一個volatile的int類型變量state來表示狀態。
AQS的核心設計思想是,如果當前線程請求的共享資源處於閒置狀態,則將該線程設置爲工作線程,並將該資源設置爲鎖定狀態。如果該資源處於鎖定狀態,則將該線程加入CLH隊列中。
那麼問題來了!什麼是CLH隊列?無需解釋,看完下圖就全明白了。
02
源碼分析
上文談到了AQS是用一個volatile的int類型變量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的一些總結,供大家參考。
相關鏈接
學習 | 工作 | 分享
????長按關注“有理想的菜雞”
只有你想不到,沒有你學不到