本篇內容介紹了“ReentrantLock核心原理是什么”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
成都創新互聯公司-專業網站定制、快速模板網站建設、高性價比承留網站開發、企業建站全套包干低至880元,成熟完善的模板庫,直接使用。一站式承留網站制作公司更省心,省錢,快速模板網站建設找我們,業務覆蓋承留地區。費用合理售后完善,10年實體公司更值得信賴。
前言
先來一個面試題:
面試官:我看你熟悉ReentrantLock源碼,能講講他的中斷鎖是怎么實現的么?
不知道也沒關系,看完這篇文章通過你的思考,能找到答案哦
那我們開始吧
ReentrantLock 中文我們叫做可重入互斥鎖,可重入的意思是同一個線程可以對同一個共享資源重復的加鎖或釋放鎖,互斥就是 AQS 中的排它鎖的意思,只允許一個線程獲得鎖。
簡單應用
ReentrantLock 的使用相比較 synchronized 會稍微繁瑣一點,所謂顯示鎖,也就是你在代碼中需要主動的去進行 lock 操作。一般來講我們可以按照下面的方式使用 ReentrantLock
Lock lock = new ReentrantLock(); lock.lock(); try { doSomething(); }finally { lock.unlock(); }
lock.lock () 就是在顯式的上鎖。上鎖后,下面的代碼塊一定要放到 try 中,并且要結合 finally 代碼塊調用lock.unlock ()來釋放鎖,否則一定 doSomething 方法中出現任何異常,這個鎖將永遠不會被釋放掉。
公平鎖和非公平鎖
synchronized 是非公平鎖,也就是說每當鎖匙放的時候,所有等待鎖的線程并不會按照排隊順去依次獲得鎖,而是會再次去爭搶鎖。ReentrantLock 相比較而言更為靈活,它能夠支持公平和非公平鎖兩種形式。只需要在聲明的時候傳入 true。
Lock lock = new ReentrantLock(true);
而默認的無參構造方法則會創建非公平鎖。
tryLock方法
前面我們通過 lock.lock (); 來完成加鎖,此時加鎖操作是阻塞的,直到獲取鎖才會繼續向下進行。ReentrantLock 其實還有更為靈活的枷鎖方式 tryLock。
tryLock 方法有兩個重載,第一個是無參數的 tryLock 方法,被調用后,該方法會立即返回獲取鎖的情況。獲取為 true,未能獲取為 false。我們的代碼中可以通過返回的結果進行進一步的處理。第二個是有參數的 tryLock 方法,通過傳入時間和單位,來控制等待獲取鎖的時長。如果超過時間未能獲取鎖則放回 false,反之返回 true。使用方法如下:
if(lock.tryLock(2, TimeUnit.SECONDS)){ try { doSomething(); } catch (InterruptedException e) { e.printStackTrace(); }finally { lock.unlock(); } }else{ doSomethingElse(); }
我們如果不希望無法獲取鎖時一直等待,而是希望能夠去做一些其它事情時,可以選擇此方式。
類結構
ReentrantLock 類本身是不繼承 AQS 的,實現了 Lock 接口,如下:
public class ReentrantLock implements Lock, java.io.Serializable {}
Lock 接口定義了各種加鎖,釋放鎖的方法,接口有如下幾個:
// 獲得鎖方法,獲取不到鎖的線程會到同步隊列中阻塞排隊 void lock(); // 獲取可中斷的鎖 void lockInterruptibly() throws InterruptedException; // 嘗試獲得鎖,如果鎖空閑,立馬返回 true,否則返回 false boolean tryLock(); // 帶有超時等待時間的鎖,如果超時時間到了,仍然沒有獲得鎖,返回 false boolean tryLock(long time, TimeUnit unit) throws InterruptedException; // 釋放鎖 void unlock(); // 得到新的 Condition Condition newCondition();
ReentrantLock 就負責實現這些接口,我們使用時,直接面對的也是這些方法,這些方法的底層實現都是交給 Sync 內部類去實現的,Sync 類的定義如下:
abstract static class Sync extends AbstractQueuedSynchronizer {}
都是最終繼承自 AbstractQueuedSynchronizer。這就是著名的 AQS。通過查看 AQS 的注釋我們了解到, AQS 依賴先進先出隊列實現了阻塞鎖和相關的同步器(信號量、事件等)。
AQS 內部有一個 volatile 類型的 state 屬性,實際上多線程對鎖的競爭體現在對 state 值寫入的競爭。一旦 state 從 0 變為 1,代表有線程已經競爭到鎖,那么其它線程則進入等待隊列。
等待隊列是一個鏈表結構的 FIFO 隊列,這能夠確保公平鎖的實現。同一線程多次獲取鎖時,如果之前該線程已經持有鎖,那么對 state 再次加 1。釋放鎖時,則會對 state-1。直到減為 0,才意味著此線程真正釋放了鎖。
Sync 繼承了 AbstractQueuedSynchronizer ,所以 Sync 就具有了鎖的框架,根據 AQS 的框架,Sync 只需要實現 AQS 預留的幾個方法即可,但 Sync 也只是實現了部分方法,還有一些交給子類 NonfairSync 和 FairSync 去實現了,NonfairSync 是非公平鎖,FairSync 是公平鎖,定義如下:
// 同步器 Sync 的兩個子類鎖 static final class FairSync extends Sync {} static final class NonfairSync extends Sync {}
幾個類整體的結構如下:
圖中 Sync、NonfairSync、FairSync 都是靜態內部類的方式實現的,這個也符合 AQS 框架定義的實現標準。
構造器
ReentrantLock 構造器有兩種,代碼如下:
// 無參數構造器,相當于 ReentrantLock(false),默認是非公平的 public ReentrantLock() { sync = new NonfairSync(); } public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }
無參構造器默認構造是非公平的鎖,有參構造器可以選擇。
從構造器中可以看出,公平鎖是依靠 FairSync 實現的,非公平鎖是依靠 NonfairSync 實現的
源碼解析
Sync同步器
Sync 表示同步器,繼承了 AQS:
從圖中可以看出,lock 方法是個抽象方法,留給 FairSync 和 NonfairSync 兩個子類去實現。
加鎖方法
FairSync公平鎖
FairSync 公平鎖只實現了 lock 和 tryAcquire 兩個方法,lock 方法非常簡單,如下:
// acquire 是 AQS 的方法,表示先嘗試獲得鎖,失敗之后進入同步隊列阻塞等待 final void lock() { acquire(1); }
在 FairSync 并沒有重寫 acquire 方法代碼。調用的為 AbstractQueuedSynchronizer 的代碼,如下:
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }
首先調用一次 tryAcquire 方法。如果 tryAcquire 方法返回 true,那么 acquire 就會立即返回。
但如果 tryAcquire 返回了 false,那么則會先調用 addWaiter,把當前線程包裝成一個等待的 node,加入到等待隊列。然后調用 acquireQueued 嘗試排隊獲取鎖,如果成功后發現自己被中斷過,那么返回 true,導致 selfInterrupt 被觸發,這個方里只是調用Thread.currentThread ().interrupt ();進行 interrupt。
acquireQueued 代碼如下:
在此方法中進入自旋,不斷查看自己排隊的情況。如果輪到自己( header 是已經獲取鎖的線程,而 header 后面的線程是排隊到要去獲取鎖的線程),那么調用 tryAcquire 方法去獲取鎖,然后把自己設置為隊列的 header。在自旋中,如果沒有排隊到自己,還會檢查是否應該應該被中斷。
整個獲取鎖的過程我們可以總結下:
直接通過 tryAcquire 嘗試獲取鎖,成功直接返回;
如果沒能獲取成功,那么把自己加入等待隊列;
自旋查看自己的排隊情況;
如果排隊輪到自己,那么嘗試通過 tryAcquire 獲取鎖;
如果沒輪到自己,那么回到第三步查看自己的排隊情況。
從以上過程我們可以看到鎖的獲取是通過 tryAcquire 方法。而這個方法在 FairSync 和 NonfairSync 有不同實現。
這個tryAcquire 方法是 AQS 在 acquire 方法中留給子類實現的抽象方法,FairSync 中實現的源碼如下:
實際上它的實現和 NonfairSync 的實現,只是在 c==0 時,多了對 hasQueuedPredecessors 方法的調用。故名思義,這個方法做的事情就是判斷當前線程是否前面還有排隊的線程。
當它前面沒有排隊線程,說明已經排隊到自己了,這是才會通過 CAS 的的方式去改變 state 值為 1,如果成功,那么說明當前線程獲取鎖成功。接下來就是調用 setExclusiveOwnerThread 把自己設置成為鎖的擁有者。else if 中邏輯則是在處理重入邏輯,如果當前線程就是鎖的擁有者,那么會把 state 加 1 更新回去。
通過以上分析,我們可以看出 AbstractQueuedSynchronizer 提供 acquire 方法的模板邏輯,但其中真正對鎖的獲取方法 tryAcquire,是在不同子類中實現的,這是很好的設計思想。
NonfairSync非公平鎖
NonfairSync 底層實現了 lock 和 tryAcquire 兩個方法,如下:
nonfairTryAcquire
以上代碼有三點需要注意:
通過判斷 AQS 的 state 的狀態來決定是否可以獲得鎖,0 表示鎖是空閑的;
else if 的代碼體現了可重入加鎖,同一個線程對共享資源重入加鎖,底層實現就是把 state + 1,并且可重入的次數是有限制的,為 Integer 的最大值;
這個方法是非公平的,所以只有非公平鎖才會用到,公平鎖是另外的實現。
無參的 tryLock 方法調用的就是此方法,tryLock 的方法源碼如下:
public boolean tryLock() { // 入參數是 1 表示嘗試獲得一次鎖 return sync.nonfairTryAcquire(1); }
其底層的調用關系(只是簡單表明調用關系,并不是完整分支圖)如下:
解鎖方法
public void unlock() { sync.release(1); }
和 lock 很像,實際調用的是 sync 實現類的 release 方法。和 lock 方法一樣,這個 release 方法在 AbstractQueuedSynchronizer 中,
if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true; } return false;
這個方法中會先執行 tryRelease,它的實現也在 AbstractQueuedSynchronizer 的子類 Sync 中,如果釋放鎖成功,那么則會通過 unparkSuccessor 方法找到隊列中第一個 waitStatus<0的線程進行喚醒。我們下面看一下 tryRelease 方法代碼:
tryRelease 方法是公平鎖和非公平鎖都公用的,在鎖釋放的時候,是沒有公平和非公平的說法的。
從代碼中可以看到,鎖最終被釋放的標椎是 state 的狀態為 0,在重入加鎖的情況下,需要重入解鎖相應的次數后,才能最終把鎖釋放,比如線程 A 對共享資源 B 重入加鎖 5 次,那么釋放鎖的話,也需要釋放 5 次之后,才算真正的釋放該共享資源了。
總結
本篇文章 ReentrantLock 的使用及其核心源代碼,其實 Lock 相關的代碼還有很多。我們可以嘗試自己去閱讀。
ReentrantLock 的設計思想是通過 FIFO 的隊列保存等待鎖的線程。通過 volatile 類型的 state 保存鎖的持有數量,從而實現了鎖的可重入性。而公平鎖則是通過判斷自己是否排隊成功,來決定是否去爭搶鎖。
然后我們了解到AQS 搭建了整個鎖架構,子類鎖只需要根據場景,實現 AQS 對應的方法即可,不僅僅是 ReentrantLock 是這樣,JUC 中的其它鎖也都是這樣,只要對 AQS 了如指掌,鎖其實非常簡單。
“ReentrantLock核心原理是什么”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注創新互聯網站,小編將為大家輸出更多高質量的實用文章!
當前文章:ReentrantLock核心原理是什么
網頁地址:http://m.newbst.com/article18/jesidp.html
成都網站建設公司_創新互聯,為您提供網站策劃、手機網站建設、標簽優化、虛擬主機、企業網站制作、軟件開發
聲明:本網站發布的內容(圖片、視頻和文字)以用戶投稿、用戶轉載內容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網站立場,如需處理請聯系客服。電話:028-86922220;郵箱:631063699@qq.com。內容未經允許不得轉載,或轉載時需注明來源: 創新互聯