您好,登錄后才能下訂單哦!
這篇文章主要介紹“Java多線程并發(fā)ReentrantLock怎么使用”的相關(guān)知識(shí),小編通過實(shí)際案例向大家展示操作過程,操作方法簡(jiǎn)單快捷,實(shí)用性強(qiáng),希望這篇“Java多線程并發(fā)ReentrantLock怎么使用”文章能幫助大家解決問題。
在 Java 中實(shí)現(xiàn)線程安全的傳統(tǒng)方式是 synchronized
關(guān)鍵字,雖然它提供了一定的同步能力,但它在使用上是嚴(yán)格的互斥同步實(shí)現(xiàn):一個(gè)線程只能獲取一次鎖,沒有給其他線程提供等待隊(duì)列等機(jī)制,以至于當(dāng)一個(gè)鎖被釋放后,任意線程都有可能獲取到鎖,沒有線程等待的優(yōu)先級(jí)順序,會(huì)導(dǎo)致重要的線程在沒有爭(zhēng)用到鎖的情況下,長(zhǎng)時(shí)間阻塞。為了解決 synchronized
的痛點(diǎn),Java 提供了 ReentrantLock
可重入鎖來(lái)提供更豐富的能力和靈活性。
ReentrantLock
是一種可重入互斥鎖,其基本能力與使用 synchronized
關(guān)鍵字相同,但拓展了一些功能。它實(shí)現(xiàn)了 Lock
接口,在訪問共享資源時(shí)提供了同步的方法。操作共享資源的代碼被加鎖和解鎖方法的調(diào)用之間,從而確保當(dāng)前線程在調(diào)用加鎖方法后,阻止其他線程試圖訪問共享資源。
ReentrantLock
由上次成功鎖定的但尚未解鎖的線程持有;當(dāng)鎖不被任何線程擁有時(shí),調(diào)用 lock
方法的線程將獲取到這個(gè) ReentrantLock
,如果當(dāng)前線程已經(jīng)擁有 ReentrantLock
,lock
方法會(huì)立即返回。
ReentrantLock 允許線程多次進(jìn)入資源鎖。當(dāng)線程第一次進(jìn)入鎖時(shí),保持計(jì)數(shù)設(shè)置為 1。在解鎖之前,線程可以再次重新進(jìn)入鎖定狀態(tài),并且每次保持計(jì)數(shù)加一。對(duì)于每個(gè)解鎖請(qǐng)求,保持計(jì)數(shù)減一,當(dāng)保持計(jì)數(shù)為 0 時(shí),資源被解鎖。
ReentrantLock
的構(gòu)造器接收一個(gè)可選的 fairness 參數(shù)(Boolean 類型)。當(dāng)設(shè)置為 true 時(shí),在線程爭(zhēng)用時(shí),鎖優(yōu)先授予等待時(shí)間最長(zhǎng)的線程訪問。否則,此鎖不保證任何特定的順序。但是請(qǐng)注意,鎖的公平性不能保證線程調(diào)度的公平性。
可重入鎖還提供了一個(gè)公平參數(shù),通過該參數(shù),鎖將遵循鎖請(qǐng)求的順序,即在線程解鎖資源后,鎖將轉(zhuǎn)到等待時(shí)間最長(zhǎng)的線程。這種公平模式是通過將 true 傳遞給鎖的構(gòu)造函數(shù)來(lái)設(shè)置的。
ReentrantLock 實(shí)現(xiàn)了 Lock 接口,所以分析源碼先從 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 接口定義了更靈活和更廣泛的鎖定操作。synchronized 關(guān)鍵字是 JVM 底層提供了 monitor 指令的形式加鎖,這導(dǎo)致了獲取多個(gè)鎖時(shí),需要按獲取順序的倒序解鎖。Lock 就是為了解決這種不夠靈活的問題而出現(xiàn)的。Lock 接口的實(shí)現(xiàn)通過允許在不同范圍內(nèi)獲取和釋放鎖以及允許多個(gè)鎖按任意順序的獲取和釋放。隨著這種靈活性的增加,額外的職責(zé)也就隨之而來(lái),synchronized 關(guān)鍵字以代碼塊的結(jié)構(gòu)加鎖,執(zhí)行完成鎖會(huì)自動(dòng)釋放,而 Lock 的實(shí)現(xiàn)則需要手動(dòng)釋放鎖,大多數(shù)情況下,
應(yīng)該使用下面的語(yǔ)句實(shí)現(xiàn):
Lock l = ...; l.lock(); try { // access the resource protected by this lock } finally { l.unlock(); }
當(dāng)鎖定和解鎖發(fā)生在不同的作用域時(shí),必須注意確保所有在持有鎖時(shí)執(zhí)行的代碼都受到 try-finally 或 try-catch 的保護(hù),以確保在必要時(shí)釋放鎖。
Lock 接口中定義的方法可以劃分為三部分:
加鎖操作
解鎖操作
newCondition
加鎖操作提供了四個(gè)方法:
// 獲取鎖,如果鎖不可用,則當(dāng)前線程將被禁用以用于線程調(diào)度目的并處于休眠狀態(tài),直到獲取到鎖為止。 void lock(); void lockInterruptibly() throws InterruptedException; boolean tryLock(); boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
lock():
獲取鎖,如果無(wú)法獲取到,則當(dāng)前線程進(jìn)入阻塞狀態(tài),直到獲取到鎖為止。
lockInterruptibly():
除非當(dāng)前線程被中斷,否則去獲取鎖。如果獲取到了鎖,則立即返回。如果沒有爭(zhēng)用到鎖,則當(dāng)前線程阻塞,直到發(fā)生下面兩種情況之一:
如果當(dāng)前線程:
以上兩種情況都會(huì)拋出 InterruptedException
,并清除當(dāng)前線程的中斷狀態(tài)。
當(dāng)前線程獲取到了鎖
其他線程中斷了當(dāng)前線程
在進(jìn)入此方法時(shí),設(shè)置了中斷狀態(tài)
在獲取鎖的過程中被中斷
tryLock()
僅當(dāng)鎖處于空閑狀態(tài)時(shí),才獲取鎖。獲取到鎖立即返回 true,如果鎖被其他線程持有,則此方法立即返回 false 。
此方法的典型用法是:
Lock lock = ...; if (lock.tryLock()) { try { // manipulate protected state } finally { lock.unlock(); } } else { // perform alternative actions }
這種用法確保鎖在獲得時(shí)解鎖,并且在未獲得鎖時(shí)不嘗試解鎖。
tryLock(long time, TimeUnit unit)
如果在給定時(shí)間內(nèi)鎖處于空閑狀態(tài),且當(dāng)前線程沒有被中斷,則獲取鎖。
如果當(dāng)前線程成功獲取到了鎖,則此方法立即返回 true ;如果當(dāng)前線程無(wú)法獲取到鎖,則當(dāng)前線程會(huì)進(jìn)入阻塞狀態(tài)直到發(fā)生下面三種情況之一:
如果進(jìn)入此方法時(shí)當(dāng)前線程處于中斷狀態(tài)或獲取鎖的過程中已進(jìn)入中斷狀態(tài),以上兩種情況都會(huì)拋出 InterruptedException
,并清除當(dāng)前線程的中斷狀態(tài)。
此外,如果 time 參數(shù)小于等于 0 ,該方法不會(huì)等待。
鎖被當(dāng)前線程成功獲取
指定時(shí)間超時(shí)
其他線程中斷了當(dāng)前線程
解鎖操作:
解鎖操作只提供了 unlock()
方法。
newCondition:
返回綁定到此 Lock 的 Condition 實(shí)例。
ReentrantLock 有三個(gè)內(nèi)部類,分別是 Sync、NonfairSync、FairSync 。
它們的繼承關(guān)系是:
這個(gè)類是 AQS 的直接實(shí)現(xiàn),它為公平鎖實(shí)現(xiàn) FairSync 和非公平鎖實(shí)現(xiàn) NonfairSync 提供了共同的基礎(chǔ)能力。
abstract static class Sync extends AbstractQueuedSynchronizer { @ReservedStackAccess final boolean tryLock() abstract boolean initialTryLock(); @ReservedStackAccess final void lock() @ReservedStackAccess final void lockInterruptibly() @ReservedStackAccess final boolean tryLockNanos(long nanos) @ReservedStackAccess protected final boolean tryRelease(int releases) protected final boolean isHeldExclusively() final ConditionObject newCondition() final Thread getOwner() final int getHoldCount() final boolean isLocked() }
下面是一些重點(diǎn)的方法講解。
這個(gè)方法執(zhí)行了一個(gè)不公平的嘗試加鎖操作:
@ReservedStackAccess final boolean tryLock() { Thread current = Thread.currentThread(); // 獲取當(dāng)前線程 int c = getState(); // 從 AQS 中獲取狀態(tài) if (c == 0) { // 當(dāng)前鎖的狀態(tài)為未被持有 if (compareAndSetState(0, 1)) { // CAS 更新狀態(tài)為加鎖狀態(tài) 1 setExclusiveOwnerThread(current); // 設(shè)置當(dāng)前持有的線程 return true; // 獲取鎖成功,return true } } else if (getExclusiveOwnerThread() == current) { // 如果當(dāng)前持有鎖的線程是當(dāng)前線程 if (++c < 0) // overflow // c 即是狀態(tài)也是計(jì)數(shù)器,可重入計(jì)數(shù) + 1 throw new Error("Maximum lock count exceeded"); setState(c); // 更新狀態(tài) return true; // 重入成功,return true } return false; // 嘗試獲取鎖失敗。 }
為什么說它是不公平的,因?yàn)檫@個(gè)方法沒有按照公平等待原則,讓等待時(shí)間最久的線程優(yōu)先獲取鎖資源。
這是一個(gè)抽象方法,用來(lái)在 lock 前執(zhí)行初始化工作。
@ReservedStackAccess final void lock() { if (!initialTryLock()) acquire(1); }
先根據(jù) initialTryLock()
進(jìn)行判斷,然后調(diào)用 acquire(1)
,acquire 方法在 AQS 中:
public final void acquire(int arg) { if (!tryAcquire(arg)) acquire(null, arg, false, false, false, 0L); }
這個(gè)方法會(huì)讓當(dāng)前線程去嘗試獲取鎖資源,并忽略中斷。通過調(diào)用 tryAcquire
至少一次來(lái)實(shí)現(xiàn),如果失敗,則去等待隊(duì)列排隊(duì),可能會(huì)導(dǎo)致阻塞。
@ReservedStackAccess final void lockInterruptibly() throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); if (!initialTryLock()) acquireInterruptibly(1); }
這個(gè)方法相當(dāng)于在 lock 方法前首先進(jìn)行了線程中斷檢查,如果沒有被中斷,也是通過 initialTryLock()
判斷是否需要執(zhí)行嘗試獲取鎖的操作。與 lock 方法不同,這里調(diào)用的是 (1)
:
public final void acquireInterruptibly(int arg) throws InterruptedException { if (Thread.interrupted() || (!tryAcquire(arg) && acquire(null, arg, false, true, false, 0L) < 0)) throw new InterruptedException(); }
對(duì)線程中斷進(jìn)行了檢查,如果線程被中斷則中止當(dāng)前操作,至少調(diào)用 1 次 tryAcquire 嘗試去獲取鎖資源。否則線程去隊(duì)列排隊(duì),此方法可能會(huì)導(dǎo)致阻塞,直到調(diào)用 tryAcquire 成功或線程被中斷。
final boolean tryLockNanos(long nanos) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); return initialTryLock() || tryAcquireNanos(1, nanos); }
public final boolean tryAcquireNanos(int arg, long nanosTimeout) throws InterruptedException { if (!Thread.interrupted()) { if (tryAcquire(arg)) return true; if (nanosTimeout <= 0L) return false; int stat = acquire(null, arg, false, true, true, System.nanoTime() + nanosTimeout); // 多了一個(gè)超時(shí)時(shí)間 if (stat > 0) return true; if (stat == 0) return false; } throw new InterruptedException(); }
本質(zhì)上調(diào)用 acquire ,多設(shè)置了一個(gè) time 參數(shù)。
@ReservedStackAccess protected final boolean tryRelease(int releases) { int c = getState() - releases; if (getExclusiveOwnerThread() != Thread.currentThread()) throw new IllegalMonitorStateException(); boolean free = (c == 0); // c = 0 說明成功釋放鎖資源 if (free) setExclusiveOwnerThread(null); setState(c); return free; }
可以看出,tryRelease 方法最終更新了 State ,進(jìn)一步說明了 AQS 的實(shí)現(xiàn),本質(zhì)上都是通過原子 int 來(lái)表示同步狀態(tài)的。
final ConditionObject newCondition() { return new ConditionObject(); }
這里的 newCondition 返回的是 AQS 的內(nèi)部類 ConditionObject 的實(shí)例。
Sync 中的方法與其含義:
static final class NonfairSync extends Sync { final boolean initialTryLock() { Thread current = Thread.currentThread(); if (compareAndSetState(0, 1)) { // 比較并設(shè)置狀態(tài)成功,狀態(tài)0表示鎖沒有被占用 setExclusiveOwnerThread(current); // 設(shè)置當(dāng)前線程為持有鎖的線程 return true; } else if (getExclusiveOwnerThread() == current) { // 重入情況 int c = getState() + 1; if (c < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(c); return true; } else return false; } protected final boolean tryAcquire(int acquires) { if (getState() == 0 && compareAndSetState(0, acquires)) { setExclusiveOwnerThread(Thread.currentThread()); return true; } return false; } }
NonfairSync 實(shí)現(xiàn)了 initialTryLock()
,其中主要是為當(dāng)前對(duì)象設(shè)置持有線程;如果是重入的情況,則 state 計(jì)數(shù) + 1 。這個(gè)方法中的邏輯和 tryLock
方法十分相似,他們都是不公平的。每次嘗試獲取鎖,都不是按照公平等待的原則,讓等待時(shí)間最久的線程獲得鎖,所以這是不公平鎖。
static final class FairSync extends Sync { private static final long serialVersionUID = -3000897897090466540L; /** * 僅在可重入或隊(duì)列為空時(shí)獲取。 */ final boolean initialTryLock() { Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { // 鎖處于可用狀態(tài) if (!hasQueuedThreads() && compareAndSetState(0, 1)) { // 查詢是否有線程正在等待獲取此鎖 setExclusiveOwnerThread(current); return true; } } else if (getExclusiveOwnerThread() == current) { if (++c < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(c); return true; } return false; } /** * 僅當(dāng)線程是隊(duì)列頭節(jié)點(diǎn)或?yàn)榭諘r(shí)獲取。 */ protected final boolean tryAcquire(int acquires) { if (getState() == 0 && !hasQueuedPredecessors() && compareAndSetState(0, acquires)) { setExclusiveOwnerThread(Thread.currentThread()); return true; } return false; } }
公平鎖依賴兩個(gè)判斷條件實(shí)現(xiàn):
hasQueuedThreads
用來(lái)查詢是否有其他線程正在等待獲取此鎖。
hasQueuedPredecessors
是用來(lái)查詢是否有其他線程比當(dāng)前線程等待的時(shí)間更長(zhǎng)。
當(dāng)存在其他線程等待時(shí)間更久時(shí),當(dāng)前線程的 tryAcquire 會(huì)直接返回 false 。
ReentrantLock 有兩個(gè)構(gòu)造函數(shù):
public ReentrantLock() { sync = new NonfairSync(); } public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }
其中一個(gè)帶有 boolean 參數(shù)的構(gòu)造方法,用來(lái)根據(jù)參數(shù) fair 實(shí)現(xiàn)公平鎖或非公平鎖,無(wú)參構(gòu)造方法默認(rèn)實(shí)現(xiàn)是非公平鎖。
private final Sync sync;
從構(gòu)造方法中就可以看出,ReentrantLock 的 sync
屬性,代表了鎖的策略(公平 or 非公平)。
sync
是一個(gè) Sync 類型的對(duì)象,繼承自 AQS ,ReentrantLock 對(duì)外暴露的方法,內(nèi)部實(shí)際上就是調(diào)用 Sync 對(duì)應(yīng)的方法實(shí)現(xiàn)的:
public class ReentrantLock implements Lock, java.io.Serializable { // ... public void lock() { sync.lock(); } public void lockInterruptibly() throws InterruptedException { sync.lockInterruptibly(); } public boolean tryLock() { return sync.tryLock(); } public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException { return sync.tryLockNanos(unit.toNanos(timeout)); } public void unlock() { sync.release(1); } public Condition newCondition() { return sync.newCondition(); } public int getHoldCount() { return sync.getHoldCount(); } public boolean isHeldByCurrentThread() { return sync.isHeldExclusively(); } public boolean isLocked() { return sync.isLocked(); } public final boolean isFair() { return sync instanceof FairSync; } protected Thread getOwner() { return sync.getOwner(); } // ... }
ReentrantLock
看起來(lái)就像是 Sync
的代理類,當(dāng)調(diào)用 ReentrantLock 對(duì)外暴露的方法時(shí),會(huì)根據(jù) sync
對(duì)象的不同的類型調(diào)用不同的實(shí)現(xiàn) 。
比如,下圖就是一個(gè)公平鎖的調(diào)用過程:
ReentrantLock.lock -> FairSync.lock -> AQS.acquire -> FairSync.tryAcquire -> AQS.hasQueuedPredecessors -> AQS.setExclusiveOwnerThread
關(guān)于“Java多線程并發(fā)ReentrantLock怎么使用”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí),可以關(guān)注億速云行業(yè)資訊頻道,小編每天都會(huì)為大家更新不同的知識(shí)點(diǎn)。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。