此文章肝了好久,圖片較多,但願你們喜歡。html
另外,感興趣的小夥伴可關注我的公衆號:一枝花算不算浪漫java
公衆號剛開始運營,但願與您一同成長。node
談到併發,咱們不得不說AQS(AbstractQueuedSynchronizer)
,所謂的AQS
便是抽象的隊列式的同步器,內部定義了不少鎖相關的方法,咱們熟知的ReentrantLock
、ReentrantReadWriteLock
、CountDownLatch
、Semaphore
等都是基於AQS
來實現的。安全
咱們先看下AQS
相關的UML
圖:markdown
思惟導圖:數據結構
AQS
中 維護了一個volatile int state
(表明共享資源)和一個FIFO
線程等待隊列(多線程爭用資源被阻塞時會進入此隊列)。多線程
這裏volatile
可以保證多線程下的可見性,當state=1
則表明當前對象鎖已經被佔有,其餘線程來加鎖時則會失敗,加鎖失敗的線程會被放入一個FIFO
的等待隊列中,比列會被UNSAFE.park()
操做掛起,等待其餘獲取鎖的線程釋放鎖纔可以被喚醒。併發
另外state
的操做都是經過CAS
來保證其併發修改的安全性。post
具體原理咱們能夠用一張圖來簡單歸納:性能
AQS
中提供了不少關於鎖的實現方法,
這裏還有一些方法並無列出來,接下來咱們以ReentrantLock
做爲突破點經過源碼和畫圖的形式一步步瞭解AQS
內部實現原理。
文章準備模擬多線程競爭鎖、釋放鎖的場景來進行分析AQS
源碼:
三個線程(線程1、線程2、線程三)同時來加鎖/釋放鎖
目錄以下:
AQS
內部實現AQS
中等待隊列的數據模型wait()
和signal()
實現原理這裏會經過畫圖來分析每一個線程加鎖、釋放鎖後AQS
內部的數據結構和實現原理
若是同時有三個線程併發搶佔鎖,此時線程一搶佔鎖成功,線程二和線程三搶佔鎖失敗,具體執行流程以下:
此時AQS
內部數據爲:
線程二、線程三加鎖失敗:
有圖能夠看出,等待隊列中的節點Node
是一個雙向鏈表,這裏SIGNAL
是Node
中waitStatus
屬性,Node
中還有一個nextWaiter
屬性,這個並未在圖中畫出來,這個到後面Condition
會具體講解的。
具體看下搶佔鎖代碼實現:
java.util.concurrent.locks.ReentrantLock .NonfairSync:
static final class NonfairSync extends Sync { final void lock() { if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); } protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } } 複製代碼
這裏使用的ReentrantLock非公平鎖,線程進來直接利用CAS
嘗試搶佔鎖,若是搶佔成功state
值回被改成1,且設置對象獨佔鎖線程爲當前線程。以下所示:
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(); 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) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; } 複製代碼
nonfairTryAcquire()
方法中首先會獲取state
的值,若是不爲0則說明當前對象的鎖已經被其餘線程所佔有,接着判斷佔有鎖的線程是否爲當前線程,若是是則累加state
值,這就是可重入鎖的具體實現,累加state
值,釋放鎖的時候也要依次遞減state
值。
若是state
爲0,則執行CAS
操做,嘗試更新state
值爲1,若是更新成功則表明當前線程加鎖成功。
以線程二爲例,由於線程一已經將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
的waitStatue
爲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
中的數據:
接着執行java.util.concurrent.locks.AbstractQueuedSynchronizer.unparkSuccessor()
方法,喚醒head
的後置節點:
private void unparkSuccessor(Node node) { int ws = node.waitStatus; if (ws < 0) compareAndSetWaitStatus(node, ws, 0); Node s = node.next; if (s == null || s.waitStatus > 0) { s = null; for (Node t = tail; t != null && t != node; t = t.prev) if (t.waitStatus <= 0) s = t; } if (s != null) LockSupport.unpark(s.thread); } 複製代碼
這裏主要是將head
節點的waitStatus
設置爲0。
此時從新將head
指針指向線程二對應的Node
節點,且使用LockSupport.unpark
方法來喚醒線程二。
被喚醒的線程二會接着嘗試獲取鎖,用CAS
指令修改state
數據。 執行完成後能夠查看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
。
接着判斷(s = h.next) == null
,這種屬於一種極端狀況,在enq()
入隊操做中,此時不是原子性操做,可能存在這種狀況:
在第一個紅框處,例如 線程一 執行完成,此時head已經有值,而還未執行tail=head
的時候,此時 線程二 判斷 head != tail
成立。而接着 線程一 執行完第二個紅框處,此時tail = node
,可是並未將head.next
指向node
。而這時 線程二 就會獲得head.next == null
成立,直接返回true。這種狀況表明有節點正在作入隊操做。
若是head.next
不爲空,那麼接着判斷head.next
節點是否爲當前線程,若是不是則返回false。你們要記清楚,返回false表明FIFO隊列中沒有等待獲取鎖的節點,此時線程能夠直接嘗試獲取鎖,若是返回true表明有等待線程,當前線程如要入隊排列,這就是體現公平鎖的地方。
非公平鎖和公平鎖的區別: 非公平鎖性能高於公平鎖性能。非公平鎖能夠減小CPU
喚醒線程的開銷,總體的吞吐效率會高點,CPU
也沒必要取喚醒全部線程,會減小喚起線程的數量
非公平鎖性能雖然優於公平鎖,可是會存在致使線程飢餓的狀況。在最壞的狀況下,可能存在某個線程一直獲取不到鎖。不過相比性能而言,飢餓問題能夠暫時忽略,這可能就是ReentrantLock
默認建立非公平鎖的緣由之一了。
上面已經介紹了AQS
所提供的核心功能,固然它還有不少其餘的特性,這裏咱們來繼續說下Condition
這個組件。
Condition
是在java 1.5
中才出現的,它用來替代傳統的Object
的wait()
、notify()
實現線程間的協做,相比使用Object
的wait()
、notify()
,使用Condition
中的await()
、signal()
這種方式實現線程間協做更加安全和高效。所以一般來講比較推薦使用Condition
其中AbstractQueueSynchronizer
中實現了Condition
中的方法,主要對外提供awaite(Object.wait())
和signal(Object.notify())
調用。
使用示例代碼:
/** * 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()
方法掛起當前線程並釋放鎖,線程二獲取鎖後使用signal
喚醒線程一。
咱們仍是用上面的demo
做爲實例,執行的流程以下:
線程一執行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()
方法是判斷當前的線程節點是否是在同步隊列中,由於上一步已經釋放了鎖,也就是說此時可能有線程已經獲取鎖同時可能已經調用了singal()
方法,若是已經喚醒,那麼就不該該park
了,而是退出while
方法,從而繼續爭搶鎖。
此時線程一被掛起,線程二獲取鎖成功。
具體流程以下圖:
線程二執行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
節點,且waitStatue
爲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會產生先喚醒再掛起的死鎖。
這裏用了一步一圖的方式結合三個線程依次加鎖/釋放鎖來展現了ReentrantLock
的實現方式和實現原理,而ReentrantLock
底層就是基於AQS
實現的,因此咱們也對AQS
有了深入的理解。
另外還介紹了公平鎖與非公平鎖的實現原理,Condition
的實現原理,基本上都是使用源碼+繪圖的講解方式,儘可能讓你們更容易去理解。
參考資料: