這篇文章主要講解了“JUC的ReentrantLock怎么使用”,文中的講解內(nèi)容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“JUC的ReentrantLock怎么使用”吧!
創(chuàng)新互聯(lián)主要業(yè)務(wù)有網(wǎng)站營銷策劃、網(wǎng)站制作、做網(wǎng)站、微信公眾號開發(fā)、小程序設(shè)計、H5場景定制、程序開發(fā)等業(yè)務(wù)。一次合作終身朋友,是我們奉行的宗旨;我們不僅僅把客戶當客戶,還把客戶視為我們的合作伙伴,在開展業(yè)務(wù)的過程中,公司還積累了豐富的行業(yè)經(jīng)驗、網(wǎng)絡(luò)營銷推廣資源和合作伙伴關(guān)系資源,并逐漸建立起規(guī)范的客戶服務(wù)和保障體系。
ReentrantLock 譯為可重入鎖,我們在使用時總是將其與 synchronized 關(guān)鍵字進行對比,實際上 ReentrantLock 與 synchronized 關(guān)鍵字在使用上具備相同的語義,區(qū)別僅在于 ReentrantLock 相對于 synchronized 關(guān)鍵字留給開發(fā)者的可操作性更強,所以在使用上更加靈活,當然凡事都有兩面,靈活的背后也暗藏著更加容易出錯的風險。
盡管語義相同,但 ReentrantLock 和 synchronized 關(guān)鍵字背后的實現(xiàn)機制卻大相徑庭。前面的文章中我們分析了 synchronized 關(guān)鍵字的實現(xiàn)內(nèi)幕,知道了 synchronized 關(guān)鍵字背后依賴于 monitor 技術(shù),而本文所要分析的 ReentrantLock 在實現(xiàn)上則依賴于 AQS 隊列同步器,具體如何基于 AQS 進行實現(xiàn),下面來一探究竟。
本小節(jié)使用 ReentrantLock 實現(xiàn)一個 3 線程交替打印的程序,演示基于 ReentrantLock 實現(xiàn)鎖的獲取、釋放,以及線程之間的通知機制。示例實現(xiàn)如下:
private static Lock lock = new ReentrantLock(true); private static Condition ca = lock.newCondition(); private static Condition cb = lock.newCondition(); private static Condition cc = lock.newCondition(); private static volatile int idx = 0; private static class A implements Runnable { @Override public void run() { try { lock.lock(); for (int i = 0; i < 10; i++) { cb.signalAll(); System.out.println("a: " + (++idx)); ca.await(); } } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } } private static class B implements Runnable { @Override public void run() { try { lock.lock(); for (int i = 0; i < 10; i++) { cc.signalAll(); System.out.println("b: " + (++idx)); cb.await(); } } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } } private static class C implements Runnable { @Override public void run() { try { lock.lock(); for (int i = 0; i < 10; i++) { ca.signalAll(); System.out.println("c: " + (++idx)); cc.await(); } } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } } public static void main(String[] args) { new Thread(new A()).start(); new Thread(new B()).start(); new Thread(new C()).start(); }
上述示例定義了 3 個線程類 A、B 和 C,并按照 A -> B -> C
的順序進行組織,各個線程在調(diào)用 Lock#lock
方法獲取到鎖之后會先嘗試通知后繼線程(將對應(yīng)的線程移入到同步隊列),然后對 idx 變量進行累加并打印,接著進入等待狀態(tài)并釋放資源,方法 Lock#unlock
接下來會調(diào)度位于同步隊列隊頭結(jié)點的線程繼續(xù)執(zhí)行。
ReentrantLock 實現(xiàn)了 Lock 接口,該接口抽象了鎖應(yīng)該具備的基本操作,包括鎖資源的獲取、釋放,以及創(chuàng)建條件對象。除了本文介紹的 ReentrantLock 外,JUC 中直接或間接實現(xiàn)了 Lock 接口的組件還包括 ReentrantReadWriteLock 和 StampedLock,我們將在后面的文章中對這些組件逐一分析。Lock 接口的定義如下:
public interface Lock { void lock(); void lockInterruptibly() throws InterruptedException; boolean tryLock(); boolean tryLock(long time, TimeUnit unit) throws InterruptedException; void unlock(); Condition newCondition(); }
各方法釋義如下:
lock()
:獲取鎖資源,如果獲取失敗則阻塞。
lockInterruptibly()
:獲取鎖資源,如果獲取失敗則阻塞,阻塞期間支持響應(yīng)中斷請求。
tryLock()
:嘗試獲取鎖資源,不管是否獲取成功都立即返回,如果獲取成功則返回 true,否則返回 false。
tryLock(long time, TimeUnit unit)
:嘗試獲取鎖資源,相對于無參版本的 tryLock 方法引入了超時機制,并支持在等待期間響應(yīng)中斷請求。
unlock()
:釋放鎖資源。
newCondition()
:創(chuàng)建一個綁定到當前 Lock 上的條件對象。
上一小節(jié)分析了 Lock 接口的定義,ReentrantLock 實現(xiàn)了該接口,并將接口方法的實現(xiàn)都委托給了 Sync 內(nèi)部類處理。Sync 是一個抽象類,繼承自 AbstractQueuedSynchronizer,并派生出 FairSync 和 NonfairSync 兩個子類(繼承關(guān)系如下圖),由命名可以看出 FairSync 實現(xiàn)了公平鎖,而 NonfairSync 則實現(xiàn)了非公平鎖。
ReentrantLock 提供了帶 boolean 參數(shù)的構(gòu)造方法,依據(jù)該參數(shù)來決定是創(chuàng)建公平鎖還是非公平鎖(默認為非公平鎖),構(gòu)造方法定義如下:
public ReentrantLock() { // 默認創(chuàng)建非公平鎖 sync = new NonfairSync(); } public ReentrantLock(boolean fair) { // 依據(jù)參數(shù)決定創(chuàng)建公平鎖還是非公平鎖 sync = fair ? new FairSync() : new NonfairSync(); }
下面將區(qū)分公平鎖和非公平鎖分析 ReentrantLock 針對 Lock 接口方法的具體實現(xiàn),在開始之前先介紹一下 AQS 中的 state 字段在 ReentrantLock 中的作用。
我們知道 ReentrantLock 是可重入的,這里的可重入是指當一個線程獲取到 ReentrantLock 鎖之后,如果該線程再次嘗試獲取該 ReentrantLock 鎖時仍然可以獲取成功,對應(yīng)的重入次數(shù)加 1。ReentrantLock 的重入次數(shù)則由 AQS 的 state 字段進行記錄。當 state 為 0 時,說明目標 ReentrantLock 鎖當前未被任何線程持有,當一個線程釋放 ReentrantLock 鎖時,對應(yīng)的 state 值需要減 1。
本小節(jié)我們來分析一下非公平鎖 NonfairSync 的實現(xiàn)機制,首先來看一下 NonfairSync#lock
方法,該方法用于獲取資源,如果獲取失敗則會將當前線程加入到同步隊列中阻塞等待。方法實現(xiàn)如下:
final void lock() { // 嘗試獲取鎖,將 state 由 0 設(shè)置為 1 if (this.compareAndSetState(0, 1)) { // 首次獲取鎖成功,記錄當前鎖對象 this.setExclusiveOwnerThread(Thread.currentThread()); } else { // 目標鎖對象已經(jīng)被占用,或者非首次獲取目標鎖對象 this.acquire(1); } }
方法 NonfairSync#lock
加鎖的過程首先會基于 CAS 操作嘗試將 ReentrantLock 的 state 值由 0 改為 1,搶占鎖資源,這也是非公平語義的根本所在。如果操作成功,則說明目標 ReentrantLock 鎖當前未被任何線程持有,且本次加鎖成功。如果操作失敗則區(qū)分兩種情況:
目標 ReentrantLock 鎖已被當前線程持有。
目標 ReentrantLock 鎖已被其它線程持有。
針對這兩種情況,接下來會調(diào)用 AbstractQueuedSynchronizer#acquire
方法嘗試獲取 1 個單位的資源,該方法由 AQS 實現(xiàn),我們已經(jīng)在前面的文章中分析過,其中會執(zhí)行模板方法 AbstractQueuedSynchronizer#tryAcquire
。NonfairSync 針對該模板方法的實現(xiàn)如下:
protected final boolean tryAcquire(int acquires) { return this.nonfairTryAcquire(acquires); }
上述方法將嘗試獲取資源的邏輯委托給 Sync#nonfairTryAcquire
方法執(zhí)行,ReentrantLock 的 ReentrantLock#tryLock()
方法同樣基于該方法實現(xiàn)。下面來分析一下該方法的執(zhí)行邏輯,實現(xiàn)如下:
final boolean nonfairTryAcquire(int acquires) { // 獲取當前線程對象 final Thread current = Thread.currentThread(); // 獲取 state 值 int c = this.getState(); if (c == 0) { // state 為 0,表示目標鎖當前未被持有,嘗試獲取鎖 if (this.compareAndSetState(0, acquires)) { this.setExclusiveOwnerThread(current); return true; } } // 如果當前已經(jīng)持有鎖的線程已經(jīng)是當前線程 else if (current == this.getExclusiveOwnerThread()) { // 重入次數(shù)加 1 int nextc = c + acquires; if (nextc < 0) { // 重入次數(shù)溢出 throw new Error("Maximum lock count exceeded"); } // 更新 state 記錄的重入次數(shù) this.setState(nextc); return true; } // 已經(jīng)持有鎖的線程不是當前線程,嘗試加鎖失敗 return false; }
方法 Sync#nonfairTryAcquire
的執(zhí)行流程可以概括為;
獲取當前 ReentrantLock 鎖的 state 值;
如果 state 值為 0,說明當前 ReentrantLock 鎖未被任何線程持有,基于 CAS 嘗試將 state 值由 0 改為 1,搶占鎖資源,修改成功即為加鎖成功;
否則,如果當前已經(jīng)持有該 ReentrantLock 鎖的線程是自己,則修改重入次數(shù)(即將 state 值加 1);
否則,目標 ReentrantLock 鎖已經(jīng)被其它線程持有,加鎖失敗。
如果 Sync#nonfairTryAcquire
方法返回 false,則說明當前線程嘗試獲取目標 ReentrantLock 鎖失敗,對于 ReentrantLock#lock
方法而言,接下去線程會被加入到同步隊列阻塞等待,而對于 ReentrantLock#tryLock()
方法而言,線程會立即退出,并返回 false。
方法 ReentrantLock#newCondition
同樣是委托給 Sync#newCondition
方法處理,該方法只是簡單的創(chuàng)建了一個 ConditionObject 對象,即新建了一個條件隊列。非公平鎖 NonfairSync 中的以下方法都是直接委托給 AQS 處理,這些方法的實現(xiàn)機制已在前面分析 AQS 時介紹過:
ReentrantLock#lockInterruptibly
:直接委托給 AbstractQueuedSynchronizer#acquireInterruptibly
方法實現(xiàn),獲取的資源數(shù)為 1。
ReentrantLock#tryLock(long, java.util.concurrent.TimeUnit)
:直接委托給 AbstractQueuedSynchronizer#tryAcquireNanos
方法實現(xiàn),獲取的資源數(shù)為 1。
ReentrantLock#unlock
:直接委托給 AbstractQueuedSynchronizer#release
方法實現(xiàn),釋放的資源數(shù)為 1。
前面的文章,我們在分析 AQS 的 AbstractQueuedSynchronizer#release
方法時,曾介紹過該方法會調(diào)用模板方法 AbstractQueuedSynchronizer#tryRelease
以嘗試釋放資源。ReentrantLock 針對該模板方法的實現(xiàn)位于 Sync 抽象類中,所以它是一個由 NonfairSync 和 FairSync 共用的方法,下面來分析一下該方法的實現(xiàn)。
protected final boolean tryRelease(int releases) { // 將當前 state 記錄的重入次數(shù)減 1 int c = this.getState() - releases; // 如果當前持有鎖的線程對象不是當前線程則拋出異常 if (Thread.currentThread() != this.getExclusiveOwnerThread()) { throw new IllegalMonitorStateException(); } boolean free = false; // 如果重入次數(shù)已經(jīng)降為 0,則清空持有當前鎖的線程對象 if (c == 0) { free = true; this.setExclusiveOwnerThread(null); } // 更新當前鎖的重入次數(shù) this.setState(c); return free; }
嘗試釋放資源的過程本質(zhì)上就是修改 state 字段值的過程,如果當前操作的線程是持有 ReentrantLock 鎖的線程,則上述方法會將 state 值減 1,即將已重入次數(shù)減 1。如果修改后的 state 字段值為 0,則說明當前線程已經(jīng)釋放了持有的 ReentrantLock 鎖,此時需要清除記錄在 ReentrantLock 對象中的線程 Thread 對象。
本小節(jié)我們來分析一下公平鎖 FairSync 的實現(xiàn)機制,這里的公平本質(zhì)上是指公平的獲取鎖資源,所以主要的區(qū)別體現(xiàn)在加鎖的過程,即 ReentrantLock#lock
方法。
前面我們在分析 NonfairSync 時看到,NonfairSync 在加鎖時首先會基于 CAS 嘗試將 state 值由 0 改為 1,失敗的情況下才會繼續(xù)調(diào)用 AbstractQueuedSynchronizer#acquire
方法等待獲取資源,并且在同步隊列中等待期間仍然會在 state 為 0 時搶占獲取鎖資源。
FairSync 相對于 NonfairSync 的區(qū)別在于當 state 值為 0 時,即目標 ReentrantLock 鎖此時未被任何線程持有的情況下,F(xiàn)airSync 并不會去搶占鎖資源,而是檢查同步隊列中是否有排在前面等待獲取鎖資源的其它線程,如果有則讓渡這些排在前面的線程優(yōu)先獲取鎖資源。
下面來看一下 FairSync#lock
方法的實現(xiàn),該方法只是簡單的將獲取鎖資源操作委托給 AQS 的 AbstractQueuedSynchronizer#acquire
方法執(zhí)行,所以我們需要重點關(guān)注一下模板方法 FairSync#tryAcquire
的實現(xiàn):
protected final boolean tryAcquire(int acquires) { // 獲取當前線程對象 final Thread current = Thread.currentThread(); // 獲取當前 state 值 int c = this.getState(); if (c == 0) { // state 為 0,表示目標鎖當前未被持有,先檢查是否有阻塞等待當前鎖的線程,如果沒有再嘗試獲取鎖 if (!this.hasQueuedPredecessors() && this.compareAndSetState(0, acquires)) { this.setExclusiveOwnerThread(current); return true; } } // 如果當前已經(jīng)持有鎖的線程已經(jīng)是當前線程,則修改已重入次數(shù)加 1 else if (current == this.getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) { throw new Error("Maximum lock count exceeded"); } this.setState(nextc); return true; } return false; } }
上述方法的執(zhí)行流程與 NonfairSync 中的相關(guān)實現(xiàn)大同小異,主要區(qū)別在于當 state 值為 0 時,F(xiàn)airSync 會調(diào)用 AbstractQueuedSynchronizer#hasQueuedPredecessors
檢查當前同步隊列中是否還有等待獲取鎖資源的其它線程,如果存在則優(yōu)先讓這些線程獲取鎖資源,并將自己加入到同步隊列中排隊等待。
感謝各位的閱讀,以上就是“JUC的ReentrantLock怎么使用”的內(nèi)容了,經(jīng)過本文的學習后,相信大家對JUC的ReentrantLock怎么使用這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是創(chuàng)新互聯(lián),小編將為大家推送更多相關(guān)知識點的文章,歡迎關(guān)注!