AQS詳解
原子性操作自:原子性在一個操作是不可中斷的,要麼全部執行成功要麼全部執行失敗,有着“同生共死”的感覺。及時在多個線程一起執行的時候,一個操作一旦開始,就不會被其他線程所幹擾2113
CAS:
全稱(Compare And Swap),比較交換,Unsafe類是CAS的核心類,提供硬件級別的原子操作。
CAS 中有三個參數:內存值 V、舊的預期值 E、要更新的值 N ,當且僅當內存值 V 的值等於舊的預期值 A 時,纔會將內存值V的值修改爲 B ,否則什麼都不幹。
CAS比較與交換的僞代碼可以表示爲:
do{
備份舊數據;
基於舊數據構造新數據;
}while(!CAS( 內存地址,備份的舊數據,新數據 ))
但是存在這樣一種情況:如果一個值原來是A,變成了B,然後又變成了A,那麼在CAS檢查的時候會發現沒有改變,但是實質上它已經發生了改變,這就是所謂的ABA問題。對於ABA問題其解決方案是加上版本號,即在每個變量都加上一個版本號,每次改變時加1,即A —> B —> A,變成1A —> 2B —> 3A。
ABA問題導致的原因,是CAS過程中只簡單進行了“值”的校驗,再有些情況下,“值”相同不會引入錯誤的業務邏輯(例如庫存),有些情況下,“值”雖然相同,卻已經不是原來的數據了。
AQS:
AQS:AbstractQuenedSynchronizer抽象的隊列式同步器。是除了java自帶的synchronized關鍵字之外的鎖機制,它提供了一種實現阻塞鎖和一系列依賴FIFO等待隊列的同步器的框架。
AQS的核心思想是,如果被請求的共享資源空閒,則將當前請求資源的線程設置爲有效的工作線程,並將共享資源設置爲鎖定狀態,如果被請求的共享資源被佔用,那麼就需要一套線程阻塞等待以及被喚醒時鎖分配的機制,這個機制AQS是用CLH隊列(FIFO)鎖實現的,即將暫時獲取不到鎖的線程加入到隊列中。
ReentrantLock、Semaphore、CountDownLatch、CyclicBarrier等併發類均是基於AQS來實現的,具體用法是通過繼承AQS實現其模板方法,然後將子類作爲同步組件的內部類。
同步狀態:
AQS 的主要使用方式是繼承,子類通過繼承同步器,並實現它的抽象方法來管理同步狀態。
AQS 使用一個 int
類型的成員變量 state
來表示同步狀態:
- 當
state > 0
時,表示已經獲取了鎖。 - 當
state = 0
時,表示釋放了鎖。
它提供了三個方法,來對同步狀態 state
進行操作,並且 AQS 可以確保對 state
的操作是安全的:
#getState()
#setState(int newState)
#compareAndSetState(int expect, int update)
鎖的獨佔與共享:
獨佔鎖:每次只能有一個線程能持有鎖,ReentrantLock就是以獨佔方式實現的互斥鎖。
共享鎖:允許多個線程同時獲取鎖,併發訪問。
AQS提供了獨佔鎖和共享鎖必須實現的方法,具有獨佔鎖功能的子類,它必須實現tryAcquire、tryRelease、isHeldExclusively等;共享鎖功能的子類,必須實現tryAcquireShared和tryReleaseShared等方法,帶有Shared後綴的方法都是支持共享鎖加鎖的語義。
獨佔鎖獲取鎖時,設置節點模式爲Node.EXCLUSIVE
獨佔式獲取同步狀態。如果當前線程獲取同步狀態成功,則由該方法返回;否則,將會進入同步隊列等待。該方法將會調用可重寫的 #tryAcquire(int arg)
方法
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
#acquireInterruptibly(int arg)
:與 #acquire(int arg)
相同,但是該方法響應中斷。當前線程爲獲取到同步狀態而進入到同步隊列中,如果當前線程被中斷,則該方法會拋出InterruptedException 異常並返回。
共享鎖獲取鎖,節點模式則爲Node.SHARED:
private void doAcquireShared(int arg) {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
主要內置方法:
#tryAcquire(int arg)
:獨佔式獲取同步狀態,獲取同步狀態成功後,其他線程需要等待該線程釋放同步狀態才能獲取同步狀態。#tryRelease(int arg)
:獨佔式釋放同步狀態。#tryAcquireShared(int arg)
:共享式獲取同步狀態,返回值大於等於 0 ,則表示獲取成功;否則,獲取失敗。#tryReleaseShared(int arg)
:共享式釋放同步狀態。#isHeldExclusively()
:當前同步器是否在獨佔式模式下被線程佔用,一般該方法表示是否被當前線程所獨佔。#tryAcquireNanos(int arg, long nanos)
:超時獲取同步狀態。如果當前線程在 nanos 時間內沒有獲取到同步狀態,那麼將會返回 false ,已經獲取則返回 true 。#acquireShared(int arg)
:共享式獲取同步狀態,如果當前線程未獲取到同步狀態,將會進入同步隊列等待,與獨佔式的主要區別是在同一時刻可以有多個線程獲取到同步狀態;#acquireSharedInterruptibly(int arg)
:共享式獲取同步狀態,響應中斷。#tryAcquireSharedNanos(int arg, long nanosTimeout)
:共享式獲取同步狀態,增加超時限制。#release(int arg)
:獨佔式釋放同步狀態,該方法會在釋放同步狀態之後,將同步隊列中第一個節點包含的線程喚醒。#releaseShared(int arg)
:共享式釋放同步狀態。
CLH同步隊列:
AQS 通過內置的 FIFO 同步隊列來完成資源獲取線程的排隊工作:
- 如果當前線程獲取同步狀態失敗(鎖)時,AQS 則會將當前線程以及等待狀態等信息構造成一個節點(Node)並將其加入同步隊列,同時會阻塞當前線程
- 當同步狀態釋放時,則會把節點中的線程喚醒,使其再次嘗試獲取同步狀態。
CLH阻塞隊列採用的是雙向鏈表隊列,頭部節點默認獲取資源獲得執行權限。後續節點不斷自旋方式查詢前置節點是否執行完成,直到頭部節點執行完成將自己的waitStatus狀態修改以通知後續節點可以獲取資源執行。
AQS中Node節點:
static final class Node {
static final Node SHARED = new Node();
static final Node EXCLUSIVE = null;
//當該線程等待超時或者被中斷,需要從同步隊列中取消等待,則該線程被置1,即被取消
//節點進入了取消狀態則不再變化
static final int CANCELLED = 1;
//後繼的節點處於等待狀態,當前節點的線程如果釋放了同步狀態或者被取消(當前節點狀態置爲-1)
//只要前繼結點釋放鎖,就會通知標識爲SIGNAL狀態的後繼結點的線程執行
static final int SIGNAL = -1;
//該節點從等待隊列中轉移到同步隊列中,加入到對同步狀態的獲取中
static final int CONDITION = -2;
//該狀態標識結點的線程處於可運行狀態
static final int PROPAGATE = -3;
volatile int waitStatus;
//前驅節點,當節點加入同步隊列的時候被設置(尾部添加)
volatile Node prev;
//後繼節點
volatile Node next;
//獲取同步狀態的線程
volatile Thread thread;
//該節點喚醒後依據該節點的狀態判斷是否依據條件喚醒下一個節點
Node nextWaiter;
//檢查當前節點是否爲共享節點
final boolean isShared() {
return nextWaiter == SHARED;
}
//查找前置節點是否存在
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
Node() { // Used to establish initial head or SHARED marker
}
Node(Thread thread, Node mode) { // Used by addWaiter
this.nextWaiter = mode;
this.thread = thread;
}
Node(Thread thread, int waitStatus) { // Used by Condition
this.waitStatus = waitStatus;
this.thread = thread;
}
}
狀態 | 判斷結果 | 說明 |
---|---|---|
waitStatus=0 | 代表初始化狀態 | 該節點尚未被初始化完成 |
waitStatus>0 | 取消狀態 | 說明該線程中斷或者等待超時,需要移除該線程 |
waitStatus<0 | 有效狀態 | 該線程處於可以被喚醒的狀態 |
nextWaiter狀態標誌 | 說明 |
---|---|
SHARED(共享模式) | 直接喚醒下一個節點 |
EXCLUSIVE(獨佔模式) | 等待當前線程執行完成後再喚醒 |
其他非空值 | 依據條件決定怎麼喚醒下一個線程。類似semaphore中控制幾個線程通過 |
首先確定自己是否爲頭部節點,如果是頭部節點則直接獲取資源開始執行,如果不是則自旋前置節點直到前置節點執行完成狀態修改爲CANCELLED,然後斷開前置節點的鏈接,獲取資源開始執行。