您好,登錄后才能下訂單哦!
這期內(nèi)容當(dāng)中小編將會給大家?guī)碛嘘P(guān)怎么深入理解ReentrantLock原理,文章內(nèi)容豐富且以專業(yè)的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。
ReentrantLock是個典型的獨占模式AQS,同步狀態(tài)為0時表示空閑。當(dāng)有線程獲取到空閑的同步狀態(tài)時,它會將同步狀態(tài)加1,將同步狀態(tài)改為非空閑,于是其他線程掛起等待。在修改同步狀態(tài)的同時,并記錄下自己的線程,作為后續(xù)重入的依據(jù),即一個線程持有某個對象的鎖時,再次去獲取這個對象的鎖是可以成功的。如果是不可重入的鎖的話,就會造成死鎖。
ReentrantLock會涉及到公平鎖和非公平鎖,實現(xiàn)關(guān)鍵在于成員變量sync
的實現(xiàn)不同,這是鎖實現(xiàn)互斥同步的核心。
//公平鎖和非公平鎖的變量 private final Sync sync; //父類 abstract static class Sync extends AbstractQueuedSynchronizer {} //公平鎖子類 static final class FairSync extends Sync {} //非公平鎖子類 static final class NonfairSync extends Sync {}
那公平鎖和非公平鎖是什么?有什么區(qū)別?
公平鎖是指當(dāng)鎖可用時,在鎖上等待時間最長的線程將獲得鎖的使用權(quán),即先進(jìn)先出。而非公平鎖則隨機(jī)分配這種使用權(quán),是一種搶占機(jī)制,是隨機(jī)獲得鎖,并不是先來的一定能先得到鎖。
ReentrantLock提供了一個構(gòu)造方法,可以實現(xiàn)公平鎖或非公平鎖:
public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }
雖然公平鎖在公平性得以保障,但因為公平的獲取鎖沒有考慮到操作系統(tǒng)對線程的調(diào)度因素以及其他因素,會影響性能。
雖然非公平模式效率比較高,但是非公平模式在申請獲取鎖的線程足夠多,那么可能會造成某些線程長時間得不到鎖,這就是非公平鎖的“饑餓”問題。
但大部分情況下我們使用非公平鎖,因為其性能比公平鎖好很多。但是公平鎖能夠避免線程饑餓,某些情況下也很有用。
接下來看看ReentrantLock公平鎖的實現(xiàn):
首先需要在構(gòu)建函數(shù)中傳入true
創(chuàng)建好公平鎖
ReentrantLock reentrantLock = new ReentrantLock(true);
調(diào)用lock()
進(jìn)行上鎖,直接acquire(1)
上鎖
public void lock() { // 調(diào)用的sync的子類FairSync的lock()方法:ReentrantLock.FairSync.lock() sync.lock(); } final void lock() { // 調(diào)用AQS的acquire()方法獲取鎖,傳的值為1 acquire(1); }
直接嘗試獲取鎖,
// AbstractQueuedSynchronizer.acquire() public final void acquire(int arg) { // 嘗試獲取鎖 // 如果失敗了,就排隊 if (!tryAcquire(arg) && // 注意addWaiter()這里傳入的節(jié)點模式為獨占模式 acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }
具體獲取鎖流程
getState()
獲取同步狀態(tài)state
值,進(jìn)行判斷是否為0:
如果狀態(tài)變量的值為0,說明暫時還沒有人占有鎖, 使用hasQueuedPredecessors()保證了不論是新的線程還是已經(jīng)排隊的線程都順序使用鎖,如果沒有其它線程在排隊,那么當(dāng)前線程嘗試更新state的值為1,并自己設(shè)置到exclusiveOwnerThread變量中,供后續(xù)自己可重入獲取鎖作準(zhǔn)備。
如果exclusiveOwnerThread中為當(dāng)前線程說明本身就占有著鎖,現(xiàn)在又嘗試獲取鎖,需要將狀態(tài)變量的值state+1
// ReentrantLock.FairSync.tryAcquire() protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); // 狀態(tài)變量的值為0,說明暫時還沒有線程占有鎖 if (c == 0) { // hasQueuedPredecessors()保證了不論是新的線程還是已經(jīng)排隊的線程都順序使用鎖 if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { // 當(dāng)前線程獲取了鎖,并將本線程設(shè)置到exclusiveOwnerThread變量中, //供后續(xù)自己可重入獲取鎖作準(zhǔn)備 setExclusiveOwnerThread(current); return true; } } // 之所以說是重入鎖,就是因為在獲取鎖失敗的情況下,還會再次判斷是否當(dāng)前線程已經(jīng)持有鎖了 else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); // 設(shè)置到state中 // 因為當(dāng)前線程占有著鎖,其它線程只會CAS把state從0更新成1,是不會成功的 // 所以不存在競爭,自然不需要使用CAS來更新 setState(nextc); return true; } return false; }
如果獲取失敗加入隊列里,那具體怎么處理呢?通過自旋的方式,隊列中線程不斷進(jìn)行嘗試獲取鎖操作,中間是可以通過中斷的方式打斷,
如果當(dāng)前節(jié)點的前一個節(jié)點為head節(jié)點,則說明輪到自己獲取鎖了,調(diào)用tryAcquire()
方法再次嘗試獲取鎖
final boolean acquireQueued(final Node node, int arg) { boolean failed = true; try { boolean interrupted = false; // 自旋 for (;;) { // 當(dāng)前節(jié)點的前一個節(jié)點, final Node p = node.predecessor(); // 如果當(dāng)前節(jié)點的前一個節(jié)點為head節(jié)點,則說明輪到自己獲取鎖了 // 調(diào)用ReentrantLock.FairSync.tryAcquire()方法再次嘗試獲取鎖 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); } }
當(dāng)前的Node的上一個節(jié)點不是Head,是需要判斷是否需要阻塞,以及尋找安全點掛起。
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { // 上一個節(jié)點的等待狀態(tài) int ws = pred.waitStatus; // 等待狀態(tài)為SIGNAL(等待喚醒),直接返回true if (ws == Node.SIGNAL) return true; // 前一個節(jié)點的狀態(tài)大于0,已取消狀態(tài) if (ws > 0) { // 把前面所有取消狀態(tài)的節(jié)點都從鏈表中刪除 do { node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } else { // 前一個Node的狀態(tài)小于等于0,則把其狀態(tài)設(shè)置為等待喚醒 compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; }
在看完獲取鎖的流程,那么你知道ReentrantLock如何實現(xiàn)公平鎖了嗎?其實就是在tryAcquire()
的實現(xiàn)中。
在tryAcquire()
的實現(xiàn)中使用了hasQueuedPredecessors()
保證了線程先進(jìn)先出FIFO的使用鎖,不會產(chǎn)生"饑餓"問題,
protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); // 狀態(tài)變量的值為0,說明暫時還沒有線程占有鎖 if (c == 0) { // hasQueuedPredecessors()保證了不論是新的線程還是已經(jīng)排隊的線程都順序使用鎖 if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { .... } ... } } public final boolean hasQueuedPredecessors() { Node t = tail; Node h = head; Node s; return h != t && ((s = h.next) == null || s.thread != Thread.currentThread()); }
tryAcquire都會檢查CLH隊列中是否仍有前驅(qū)的元素,如果仍然有那么繼續(xù)等待,通過這種方式來保證先來先服務(wù)的原則。
那這樣ReentrantLock如何實現(xiàn)可重入?是怎么重入的?
其實也很簡單,在獲取鎖后,設(shè)置一個標(biāo)識變量為當(dāng)前線程exclusiveOwnerThread
,當(dāng)線程再次進(jìn)入判斷exclusiveOwnerThread
變量是否等于本線程來判斷.
protected final boolean tryAcquire(int acquires) { // 狀態(tài)變量的值為0,說明暫時還沒有線程占有鎖 if (c == 0) { if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { // 當(dāng)前線程獲取了鎖,并將本線程設(shè)置到exclusiveOwnerThread變量中, //供后續(xù)自己可重入獲取鎖作準(zhǔn)備 setExclusiveOwnerThread(current); return true; } } //之所以說是重入鎖,就是因為在獲取鎖失敗的情況下,還會再次判斷是否當(dāng)前線程已經(jīng)持有鎖了 else if (current == getExclusiveOwnerThread()) { ... } }
當(dāng)看完公平鎖獲取鎖的流程,那其實我們也了解非公平鎖獲取鎖,那我們來看看。
其實非公平鎖獲取鎖獲取區(qū)別主要在于:
構(gòu)建函數(shù)中傳入false
或者為null,為創(chuàng)建非公平鎖NonfairSync
,true
創(chuàng)建公平鎖,
非公平鎖在獲取鎖的時候,先去檢查state
狀態(tài),再直接執(zhí)行aqcuire(1)
,這樣可以提高效率,
final void lock() { if (compareAndSetState(0, 1)) //修改同步狀態(tài)的值成功的話,設(shè)置當(dāng)前線程為獨占的線程 setExclusiveOwnerThread(Thread.currentThread()); else //獲取鎖 acquire(1); }
在tryAcquire()
中沒有hasQueuedPredecessors()
保證了不論是新的線程還是已經(jīng)排隊的線程都順序使用鎖。
其他功能都類似。在理解了獲取鎖下,我們更好理解ReentrantLock::unlock()鎖的釋放,也比較簡單。
釋放當(dāng)前線程占用的鎖
protected final boolean tryRelease(int releases) { // 計算釋放后state值 int c = getState() - releases; // 如果不是當(dāng)前線程占用鎖,那么拋出異常 if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; if (c == 0) { // 鎖被重入次數(shù)為0,表示釋放成功 free = true; // 清空獨占線程 setExclusiveOwnerThread(null); } // 更新state值 setState(c); return free; }
若釋放成功,就需要喚醒等待隊列中的線程,先查看頭結(jié)點的狀態(tài)是否為SIGNAL,如果是則喚醒頭結(jié)點的下個節(jié)點關(guān)聯(lián)的線程,如果釋放失敗那么返回false表示解鎖失敗。
設(shè)置waitStatus為0,
當(dāng)頭結(jié)點下一個節(jié)點不為空的時候,會直接喚醒該節(jié)點,如果該節(jié)點為空,則會隊尾開始向前遍歷,找到最后一個不為空的節(jié)點,然后喚醒。
private void unparkSuccessor(Node node) { int ws = node.waitStatus; if (ws < 0) compareAndSetWaitStatus(node, ws, 0); Node s = node.next;//這里的s是頭節(jié)點(現(xiàn)在是頭節(jié)點持有鎖)的下一個節(jié)點,也就是期望喚醒的節(jié)點 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); //喚醒s代表的線程 }
綜合上面的ReentrantLock的可重入,可實現(xiàn)公平\非公平鎖的特性外,還具有哪些特性?
支持線程中斷,只是在線程上增加一個中斷標(biāo)志interrupted
,并不會對運行中的線程有什么影響,具體需要根據(jù)這個中斷標(biāo)志干些什么,用戶自己去決定。比如,實現(xiàn)了等待鎖的時候,5秒沒有獲取到鎖,中斷等待,線程繼續(xù)做其它事情。
超時機(jī)制,在ReetrantLock::tryLock(long timeout, TimeUnit unit)
提供了超時獲取鎖的功能。它的語義是在指定的時間內(nèi)如果獲取到鎖就返回true,獲取不到則返回false。這種機(jī)制避免了線程無限期的等待鎖釋放。
ReentrantLock支持等待可中斷,可以中斷等待中的線程
ReentrantLock可實現(xiàn)公平鎖
ReentrantLock可實現(xiàn)選擇性通知,即可以有多個Condition隊列
場景1:如果已加鎖,則不再重復(fù)加鎖,多用于進(jìn)行非重要任務(wù)防止重復(fù)執(zhí)行,如,清除無用臨時文件,檢查某些資源的可用性,數(shù)據(jù)備份操作等
場景2:如果發(fā)現(xiàn)該操作已經(jīng)在執(zhí)行,則嘗試等待一段時間,等待超時則不執(zhí)行,防止由于資源處理不當(dāng)長時間占用導(dǎo)致死鎖情況
場景3:如果發(fā)現(xiàn)該操作已經(jīng)加鎖,則等待一個一個加鎖,主要用于對資源的爭搶(如:文件操作,同步消息發(fā)送,有狀態(tài)的操作等)
場景4:可中斷鎖,取消正在同步運行的操作,來防止不正常操作長時間占用造成的阻塞
上述就是小編為大家分享的怎么深入理解ReentrantLock原理了,如果剛好有類似的疑惑,不妨參照上述分析進(jìn)行理解。如果想知道更多相關(guān)知識,歡迎關(guān)注億速云行業(yè)資訊頻道。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。