轉載自:https://mp.weixin.qq.com/s/trsjgUFRrz40Simq2VKxTA
概述
AQS(AbstractQueuedSynchronizer),所謂的AQS即是抽象的隊列式的同步器,內部定義了很多鎖相關的方法,我們熟知的ReentrantLock、ReentrantReadWriteLock、CountDownLatch、Semaphore等都是基於AQS來實現的。
實現原理
volatile int state
資源狀態。當state=1
則代表當前對象鎖已經被佔有,其他線程來加鎖時則會失敗,加鎖失敗的線程會被放入一個等待隊列中,比列會被UNSAFE.park()
操作掛起,等待其他獲取鎖的線程釋放鎖才能夠被喚醒。state
的操作都是通過CAS
來保證其併發修改的安全性。- 線程等待隊列,多線程爭用資源被阻塞時會進入此隊列
場景分析
三個線程(線程一、線程二、線程三)同時來加鎖/釋放鎖
線程一加鎖成功
如果同時有三個線程併發搶佔鎖,此時線程一搶佔鎖成功,線程二和線程三搶佔鎖失敗,具體執行流程如下:
此時AQS內部數據爲:
線程二、線程三加鎖失敗:
等待隊列中的節點Node是一個雙向鏈表,這裏SIGNAL
是Node
中waitStatus
屬性,Node
中還有一個nextWaiter
屬性,這個並未在圖中畫出來,這個到後面Condition
會具體講解的。
搶佔鎖代碼實現:java.util.concurrent.locks.ReentrantLock .NonfairSync:
//這裏使用的ReentrantLock非公平鎖
static final class NonfairSync extends Sync {
final void lock() {
//線程進來直接利用CAS嘗試搶佔鎖,如果搶佔成功state值回被改爲1,
if (compareAndSetState(0, 1))
//且設置對象獨佔鎖線程爲當前線程
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
protected final void setExclusiveOwnerThread(Thread thread) {
exclusiveOwnerThread = thread;
}
線程二搶佔鎖失敗
線程一搶佔鎖成功後,state變爲1,線程二通過CAS修改state變量必然會失敗。此時AQS中FIFO(First In First Out 先進先出)隊列中數據如圖所示:
線程二執行的邏輯一步步拆解來看:
java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire()
:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
tryAcquire()
的具體實現:java.util.concurrent.locks.ReentrantLock .nonfairTryAcquire()
:
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
//nonfairTryAcquire()方法中首先會獲取state的值
int c = getState();
//如果state爲0,則執行CAS操作,嘗試更新state值爲1,如果更新成功則代表當前線程加鎖成功。
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//如果不爲0則說明當前對象的鎖已經被其他線程所佔有,
//接着判斷佔有鎖的線程是否爲當前線程,
//如果是則累加state值,這就是可重入鎖的具體實現,累加state值,
//釋放鎖的時候也要依次遞減state值。
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
以線程二爲例,因爲線程一已經將state
修改爲1,所以線程二通過CAS
修改state
的值不會成功。加鎖失敗。
線程二執行tryAcquire()
後會返回false,接着執行addWaiter(Node.EXCLUSIVE)
邏輯,將自己加入到一個FIFO
等待隊列中,代碼實現如下:
java.util.concurrent.locks.AbstractQueuedSynchronizer.addWaiter()
:
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
這段代碼首先會創建一個和當前線程綁定的Node節點,Node爲雙向鏈表。此時等待對內中的tail指針爲空,直接調用enq(node)方法將當前線程加入等待隊列尾部:
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) {
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
第一遍循環時tail
指針爲空,進入if
邏輯,使用CAS
操作設置head
指針,將head
指向一個新創建的Node
節點。此時AQS中數據:
執行完成之後,head
、tail
、t
都指向第一個Node
元素。
接着執行第二遍循環,進入else
邏輯,此時已經有了head
節點,這裏要操作的就是將線程二對應的Node節點掛到head節點後面。此時隊列中就有了兩個Node
節點:
addWaiter()
方法執行完後,會返回當前線程創建的節點信息。繼續往後執行acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
邏輯,此時傳入的參數爲線程二對應的Node節點信息:
java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued()
:
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndChecknIterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
return true;
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
acquireQueued()
這個方法會先判斷當前傳入的Node
對應的前置節點是否爲head
,如果是則嘗試加鎖。加鎖成功過則將當前節點設置爲head
節點,然後空置之前的head
節點,方便後續被垃圾回收掉。
如果加鎖失敗或者Node
的前置節點不是head節點,就會通過shouldParkAfterFailedAcquire
方法 將head
節點的waitStatus
變爲了SIGNAL=-1
,最後執行parkAndChecknIterrupt
方法,調用LockSupport.park()
掛起當前線程。
此時AQS中的數據如下圖:
此時線程二就靜靜的待在AQS的等待隊列裏面了,等着其他線程釋放鎖來喚醒它。
線程三搶佔鎖失敗
看完了線程二搶佔鎖失敗的分析,那麼再來分析線程三搶佔鎖失敗就很簡單了,先看看addWaiter(Node mode)
方法:
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
此時等待隊列的tail
節點指向線程二,進入if
邏輯後,通過CAS
指令將tail
節點重新指向線程三。接着線程三調用enq()
方法執行入隊操作,和上面線程二執行方式是一致的,入隊後會修改線程二對應的Node
中的waitStatus=SIGNAL
。最後線程三也會被掛起。此時等待隊列的數據如圖:
線程一釋放鎖
現在來分析下釋放鎖的過程,首先是線程一釋放鎖,釋放鎖後會喚醒head
節點的後置節點,也就是現在的線程二,具體操作流程如下:
執行完後等待隊列數據如下:
此時線程二已經被喚醒,繼續嘗試獲取鎖,如果獲取鎖失敗,則會繼續被掛起。如果獲取鎖成功,則AQS中數據如圖:
先看看線程一釋放鎖的代碼:
java.util.concurrent.locks.AbstractQueuedSynchronizer.release()
:
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
這裏首先會執行tryRelease()
方法,這個方法具體實現在ReentrantLock
中,如果tryRelease
執行成功,則繼續判斷head
節點的waitStatus
是否爲0,前面我們已經看到過,head
的waitStatus
爲SIGNAL(-1)
,這裏就會執行unparkSuccessor()
方法來喚醒head
的後置節點,也就是我們上面圖中線程二對應的Node
節點。
此時看ReentrantLock.tryRelease()
中的具體實現:
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;
}
執行完ReentrantLock.tryRelease()
後,state
被設置成0,Lock對象的獨佔鎖被設置爲null。此時看下AQS中的數據:
此時線程二被喚醒,線程二接着之前被park
的地方繼續執行,繼續執行acquireQueued()
方法。
線程二喚醒繼續加鎖
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
此時線程二被喚醒,繼續執行for循環,判斷線程二的前置節點是否爲head,如果是則繼續使用tryAcquire()
方法來嘗試獲取鎖,其實就是使用CAS
操作來修改state
值,如果修改成功則代表獲取鎖成功。接着將線程二設置爲head
節點,然後空置之前的head
節點數據,被空置的節點數據等着被垃圾回收。
此時線程三獲取鎖成功,AQS中隊列數據如下:
線程二釋放鎖/線程三加鎖
當線程二釋放鎖時,會喚醒被掛起的線程三,流程和上面大致相同,被喚醒的線程三會再次嘗試加鎖,具體代碼可以參考上面內容。具體流程圖如下:
此時AQS中隊列數據如圖:
公平鎖實現原理
上面所有的加鎖場景都是基於非公平鎖來實現的,非公平鎖是ReentrantLock
的默認實現,那我們接着來看一下公平鎖的實現原理,這裏先用一張圖來解釋公平鎖和非公平鎖的區別:
非公平鎖執行流程:
當線程二釋放鎖的時候,喚醒被掛起的線程三,線程三執行tryAcquire()
方法使用CAS操作來嘗試修改state
值,如果此時又來了一個線程四也來執行加鎖操作,同樣會執行tryAcquire()
方法。
這種情況就會出現競爭,線程四如果獲取鎖成功,線程三仍然需要待在等待隊列中被掛起。這就是所謂的非公平鎖,線程三辛辛苦苦排隊等到自己獲取鎖,卻眼巴巴的看到線程四插隊獲取到了鎖。
公平鎖執行流程:
公平鎖在加鎖的時候,會先判斷AQS
等待隊列中是存在節點,如果存在節點則會直接入隊等待,具體代碼如下.
公平鎖在獲取鎖是也是首先會執行acquire()
方法,只不過公平鎖單獨實現了tryAcquire()
方法:
java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire()
:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
這裏會執行ReentrantLock
中公平鎖的tryAcquire()
方法
java.util.concurrent.locks.ReentrantLock.FairSync.tryAcquire()
:
static final class FairSync extends Sync {
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;
}
}
這裏會先判斷state
值,如果不爲0且獲取鎖的線程不是當前線程,直接返回false代表獲取鎖失敗,被加入等待隊列。如果是當前線程則可重入獲取鎖。
如果state=0
則代表此時沒有線程持有鎖,執行hasQueuedPredecessors()
判斷AQS等待隊列中是否有元素存在,如果存在其他等待線程,那麼自己也會加入到等待隊列尾部,做到真正的先來後到,有序加鎖。具體代碼如下:
java.util.concurrent.locks.AbstractQueuedSynchronizer.hasQueuedPredecessors()
:
public final boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
這段代碼很有意思,返回false代表隊列中沒有節點或者僅有一個節點是當前線程創建的節點。返回true則代表隊列中存在等待節點,當前線程需要入隊等待。
先判斷head
是否等於tail
,如果隊列中只有一個Node
節點,那麼head
會等於tail
,接着判斷head
的後置節點,這裏肯定會是null
,如果此Node
節點對應的線程和當前的線程是同一個線程,那麼則會返回false
,代表沒有等待節點或者等待節點就是當前線程創建的Node節點。此時當前線程會嘗試獲取鎖。
如果head
和tail
不相等,說明隊列中有等待線程創建的節點,此時直接返回true
,如果只有一個節點,而此節點的線程和當前線程不一致,也會返回true
非公平鎖和公平鎖的區別:非公平鎖性能高於公平鎖性能。非公平鎖可以減少CPU喚醒線程的開銷,整體的吞吐效率會高點,CPU也不必取喚醒所有線程,會減少喚起線程的數量
非公平鎖性能雖然優於公平鎖,但是會存在導致線程飢餓的情況。在最壞的情況下,可能存在某個線程一直獲取不到鎖。不過相比性能而言,飢餓問題可以暫時忽略,這可能就是ReentrantLock默認創建非公平鎖的原因之一了。
Condition實現原理
Condition
是在java 1.5中才出現的,它用來替代傳統的Object
的wait()
、notify()
實現線程間的協作,相比使用Object
的wait()
、notify()
,使用Condition
中的await()
、signal()
這種方式實現線程間協作更加安全和高效。因此通常來說比較推薦使用Condition
其中AbstractQueueSynchronizer
中實現了Condition
中的方法,主要對外提供await(Object.wait())
和signal(Object.notify())
調用。
demo
/**
* ReentrantLock 實現源碼學習
* @author 一枝花算不算浪漫
* @date 2020/4/28 7:20
*/
public class ReentrantLockDemo {
static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
Condition condition = lock.newCondition();
new Thread(() -> {
lock.lock();
try {
System.out.println("線程一加鎖成功");
System.out.println("線程一執行await被掛起");
condition.await();
System.out.println("線程一被喚醒成功");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
System.out.println("線程一釋放鎖成功");
}
}).start();
new Thread(() -> {
lock.lock();
try {
System.out.println("線程二加鎖成功");
condition.signal();
System.out.println("線程二喚醒線程一");
} finally {
lock.unlock();
System.out.println("線程二釋放鎖成功");
}
}).start();
}
}
執行的流程如下:
線程一執行await()
方法:
先看下具體的代碼實現,java.util.concurrent.locks.AbstractQueuedSynchronizer.ConditionObject.await()
:
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
await()
方法中首先調用addConditionWaiter()
將當前線程加入到Condition
隊列中。
執行完後我們可以看下Condition隊列中的數據:
private Node addConditionWaiter() {
Node t = lastWaiter;
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
這裏會用當前線程創建一個Node
節點,waitStatus
爲CONDITION
。接着會釋放該節點的鎖,調用之前解析過的release()
方法,釋放鎖後此時會喚醒被掛起的線程二,線程二會繼續嘗試獲取鎖。
接着調用isOnSyncQueue()
方法判斷當前節點是否爲Condition
隊列中的頭部節點,如果是則調用LockSupport.park(this)
掛起Condition
中當前線程。此時線程一被掛起,線程二獲取鎖成功。
具體流程如下圖:
線程二執行signal()
方法:
首先我們考慮下線程二已經獲取到鎖,此時AQS等待隊列中已經沒有了數據。
接着就來看看線程二喚醒線程一的具體執行流程:
public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
先判斷當前線程是否爲獲取鎖的線程,如果不是則直接拋出異常。接着調用doSignal()
方法來喚醒線程。
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
final boolean transferForSignal(Node node) {
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
Node p = enq(node);
int ws = p.waitStatus;
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
/**
* Inserts node into queue, initializing if necessary. See picture above.
* @param node the node to insert
* @return node's predecessor
*/
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
這裏先從transferForSignal()
方法來看,通過上面的分析我們知道Condition
隊列中只有線程一創建的一個Node
節點,且waitStatus
爲CONDITION
,先通過CAS
修改當前節點waitStatus
爲0,然後執行enq()
方法將當前線程加入到等待隊列中,並返回當前線程的前置節點。
加入等待隊列的代碼在上面也已經分析過,此時等待隊列中數據如下圖:
接着開始通過CAS
修改當前節點的前置節點waitStatus
爲SIGNAL
,並且喚醒當前線程。此時AQS
中等待隊列數據爲:、
線程一被喚醒後,繼續執行await()方法中的 while 循環。
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
因爲此時線程一的waitStatus
已經被修改爲0,所以執行isOnSyncQueue()
方法會返回false
。跳出while
循環。
接着執行acquireQueued()
方法,這裏之前也有講過,嘗試重新獲取鎖,如果獲取鎖失敗繼續會被掛起。直到另外線程釋放鎖才被喚醒。
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
此時線程一的流程都已經分析完了,等線程二釋放鎖後,線程一會繼續重試獲取鎖,流程到此終結。
我們總結下 Condition 和 wait/notify 的比較:
-
Condition 可以精準的對多個不同條件進行控制,wait/notify 只能和 synchronized 關鍵字一起使用,並且只能喚醒一個或者全部的等待隊列;
-
Condition 需要使用 Lock 進行控制,使用的時候要注意 lock() 後及時的 unlock(),Condition 有類似於 await 的機制,因此不會產生加鎖方式而產生的死鎖出現,同時底層實現的是 park/unpark 的機制,因此也不會產生先喚醒再掛起的死鎖,一句話就是不會產生死鎖,但是 wait/notify 會產生先喚醒再掛起的死鎖。