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 內部簡單來說其實主要是由三部分組成的
-
state 這個狀態用來聲明對象是否已經被線程占有,狀態為 0 表示沒有,state > 0則表示已經被其他線程占有
-
當前線程 聲明當前占有的線程
-
排隊隊列 等待占有該對象的線程
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、如果有值則插入隊列等待排隊喚醒