AbstractQueuedSynchronizer
-
AQS的全稱為(AbstractQueuedSynchronizer),這個類在java.util.concurrent.locks包
-
AQS的核心思想是,如果被請求的共享資源空閒,則將當前請求資源的執行緒設定為有效的工作執行緒,並將共享資源設定為鎖定狀態,如果被請求的共享資源被佔用,那麼就需要一套執行緒阻塞等待以及被喚醒時鎖分配的機制,這個機制AQS是用CLH佇列鎖實現的,即將暫時獲取不到鎖的執行緒加入到佇列中。
-
CLH(Craig,Landin,and Hagersten)佇列是一個虛擬的雙向佇列,虛擬的雙向佇列即不存在佇列例項,僅存在節點之間的關聯關係。
-
用大白話來說,AQS就是基於CLH佇列,用volatile修飾共享變數state,執行緒通過CAS去改變狀態符,成功則獲取鎖成功,失敗則進入等待佇列,等待被喚醒。
-
CAS(Compare and Swap) 科普
如字面意思,就是先比較再替換,
cas方法有三個重要引數 : 待比較的值、預期值、要修改的新值, 如果預期值是待比較的值一致,那麼就把 要修改的值賦值給待比較的值
虛擬碼如下:
Object old; // 待比較的值 Object new; // 預期值 Object update;// 要修改的新值 if(old == new) { old = update; } 複製程式碼
CAS(比較並交換)是CPU指令級的操作,只有一步原子操作,所以非常快。而且CAS避免了請求作業系統來裁定鎖的問題,不用麻煩作業系統,直接在CPU內部就搞定了。
java中操作cas:
sun.misc.Unsafe unsafe = sun.misc.Unsafe.getUnsafe(); Demo old = new Demo(); long offset = unsafe.objectFieldOffset (Demo.class.getDeclaredField("id")) // 如果 old.id 是 1, 那麼修改成 2 return unsafe.compareAndSwapObject(old, offset, 1, 2); 複製程式碼
-
AQS定義兩種資源共享方式:Exclusive(獨佔,只有一個執行緒能執行,如ReentrantLock)和Share(共享,多個執行緒可同時執行,如Semaphore/CountDownLatch)
-
獨佔鎖API
-
isHeldExclusively():該執行緒是否正在獨佔資源。只有用到condition才需要去實現它。
aqs未實現該方法,需要手動實現,判斷當前執行緒是否已獲得資源,返回boolean
-
tryAcquire(int):獨佔方式。嘗試獲取資源,成功則返回true,失敗則返回false。
aqs抽象類未實現該方法, 一般都需要重寫該方法
-
tryRelease(int):獨佔方式。嘗試釋放資源,成功則返回true,失敗則返回false。
aqs抽象類未實現該方法,一般需要重寫該方法來釋放資源
-
-
共享鎖API
-
tryAcquireShared(int):共享方式。嘗試獲取資源。負數表示失敗;0表示成功,但沒有剩餘可用資源;正數表示成功,且有剩餘資源。
aqs抽象類未實現該方法, 一般都需要重寫該方法
-
tryReleaseShared(int):共享方式。嘗試釋放資源,如果釋放後允許喚醒後續等待結點返回true,否則返回false。aqs抽象類未實現該方法,一般需要重寫該方法來釋放資源
-
-
獨佔api和共享api的區別
- 當AQS的子類實現獨佔功能時,如 ReentrantLock,資源是否可以被訪問被定義為:只要AQS的state變數不為0,並且持有鎖的執行緒不是當前執行緒,那麼代表資源不可訪問。
- 當AQS的子類實現共享功能時,如CountDownLatch,資源是否可以被訪問被定義為:只要AQS的state變數不為0,那麼代表資源不可以為訪問。
- ReentrantReadWriteLock 中讀鎖用的共享鎖, 寫鎖用的獨佔鎖
-
獨佔鎖例子
- ReentrantLock為例,(可重入獨佔式鎖):state初始化為0,表示未鎖定狀態,A執行緒lock()時,會呼叫**tryAcquire() **獨佔鎖並將state+1.之後其他執行緒再想tryAcquire的時候就會失敗,直到A執行緒unlock()到state=0為止,其他執行緒才有機會獲取該鎖。A釋放鎖之前,自己也是可以重複獲取此鎖(state累加),這就是可重入的概念。 注意:獲取多少次鎖就要釋放多少次鎖,保證state是能回到零態的。
-
共享鎖例子
- 以CountDownLatch為例,任務分N個子執行緒去執行,state就初始化 為N,N個執行緒並行執行,每個執行緒執行完之後countDown()一次,state就會CAS減一。當N子執行緒全部執行完畢,state=0,會unpark()主呼叫執行緒,主呼叫執行緒就會從await()函式返回,繼續之後的動作。
一般來說,自定義同步器要麼是獨佔方法,要麼是共享方式,他們也只需實現tryAcquire-tryRelease、tryAcquireShared-tryReleaseShared中的一種即可。但AQS也支援自定義同步器同時實現獨佔和共享兩種方式,如ReentrantReadWriteLock。 在acquire() acquireShared()兩種方式下,執行緒在等待佇列中都是忽略中斷的,acquireInterruptibly()/acquireSharedInterruptibly()是支援響應中斷的。
-
**注意:AQS是自旋鎖:**在等待喚醒的時候,經常會使用自旋(while(!cas()))的方式,不停地嘗試獲取鎖,直到被其他執行緒獲取成功
aqs的一個獨佔鎖簡單DEMO
private static class Sync extends AbstractQueuedSynchronizer {
// 判斷是否鎖定狀態
protected boolean isHeldExclusively() {
return getState() == 1;
}
// 嘗試獲取資源,立即返回。成功則返回true,否則false。
public boolean tryAcquire(int acquires) {
assert acquires == 1; // 這裡限定只能為1個量
if (compareAndSetState(0, 1)) {//state為0才設定為1,不可重入!
setExclusiveOwnerThread(Thread.currentThread());//設定為當前執行緒獨佔資源
return true;
}
return false;
}
// 嘗試釋放資源,立即返回。成功則為true,否則false。
protected boolean tryRelease(int releases) {
assert releases == 1; // 限定為1個量
if (getState() == 0)//既然來釋放,那肯定就是已佔有狀態了。只是為了保險,多層判斷!
throw new IllegalMonitorStateException();
setExclusiveOwnerThread(null);
setState(0);//釋放資源,放棄佔有狀態
return true;
}
}
複製程式碼
aqs共享鎖demo:
// 拷貝至 RocketMQ原始碼, 基於boolean的共享鎖
public class BooleanMutex {
private Sync sync;
public BooleanMutex() {
sync = new Sync();
set(false);
}
public BooleanMutex(Boolean mutex) {
sync = new Sync();
set(mutex);
}
/**
* 阻塞等待Boolean為true
*
* @throws InterruptedException
*/
public void get() throws InterruptedException {
sync.innerGet();
}
/**
* 阻塞等待Boolean為true,允許設定超時時間
*
* @param timeout
* @param unit
* @throws InterruptedException
* @throws TimeoutException
*/
public void get(long timeout, TimeUnit unit) throws InterruptedException, TimeoutException {
sync.innerGet(unit.toNanos(timeout));
}
/**
* 重新設定對應的Boolean mutex
*
* @param mutex
*/
public void set(Boolean mutex) {
if (mutex) {
sync.innerSetTrue();
} else {
sync.innerSetFalse();
}
}
public boolean state() {
return sync.innerState();
}
/**
* Synchronization control for BooleanMutex. Uses AQS sync state to
* represent run status
*/
private final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 2559471934544126329L;
/**
* State value representing that TRUE
*/
private static final int TRUE = 1;
/**
* State value representing that FALSE
*/
private static final int FALSE = 2;
private boolean isTrue(int state) {
return (state & TRUE) != 0;
}
/**
* 實現AQS的介面,獲取共享鎖的判斷
*/
@Override
protected int tryAcquireShared(int state) {
// 如果為true,直接允許獲取鎖物件
// 如果為false,進入阻塞佇列,等待被喚醒
return isTrue(getState()) ? 1 : -1;
}
/**
* 實現AQS的介面,釋放共享鎖的判斷
*/
@Override
protected boolean tryReleaseShared(int ignore) {
// 始終返回true,代表可以release
return true;
}
boolean innerState() {
return isTrue(getState());
}
void innerGet() throws InterruptedException {
acquireSharedInterruptibly(0);
}
void innerGet(long nanosTimeout) throws InterruptedException, TimeoutException {
if (!tryAcquireSharedNanos(0, nanosTimeout)) throw new TimeoutException();
}
void innerSetTrue() {
for (; ; ) {
int s = getState();
if (s == TRUE) {
return; // 直接退出
}
if (compareAndSetState(s, TRUE)) {// cas更新狀態,避免併發更新true操作
releaseShared(0);// 釋放一下鎖物件,喚醒一下阻塞的Thread
return;
}
}
}
void innerSetFalse() {
for (; ; ) {
int s = getState();
if (s == FALSE) {
return; // 直接退出
}
if (compareAndSetState(s, FALSE)) {// cas更新狀態,避免併發更新false操作
return;
}
}
}
}
}
複製程式碼
總結:
AQS分為獨佔鎖和共享鎖兩種模式:
-
獨佔鎖
執行**acquire(int arg)**時,只能有一個執行緒,多個執行緒的話會被加入到阻塞佇列中同步執行
執行**release()**時喚醒下一個執行緒
tryAcquire(int arg)返回的是boolean值
-
共享鎖
執行**acquireShared(int arg)**時,可以多個執行緒進行爭搶
執行**releaseShared()**時如果成功, 則把佇列中所有的執行緒全部執行
**releaseShared()**完成後才返回
tryAcquireShared(int arg)返回的是int型別的值