摘要:二什么是重入鎖可重入鎖,顧名思義,支持重新進入的鎖,其表示該鎖能支持一個線程對資源的重復加鎖。將由最近成功獲得鎖,并且還沒有釋放該鎖的線程所擁有。可以使用和方法來檢查此情況是否發生。
一、寫在前面
前幾篇我們具體的聊了AQS原理以及底層源碼的實現,具體參見
《J.U.C|一文搞懂AQS》
《J.U.C|同步隊列(CLH)》
《J.U.C|AQS獨占式源碼分析》
《J.U.C|AQS共享式源碼分析》
本章我們來聊一聊其實現之一 可重入鎖ReentrantLock的實現原理以及源碼分析。
注 :本章主要講解非公平鎖的實現流程和源碼解析,其中涉及到AQS底層的實現因在前面幾章都已經詳細聊過在這會一筆帶過。
二、什么是重入鎖可重入鎖 ReentrantLock ,顧名思義,支持重新進入的鎖,其表示該鎖能支持一個線程對資源的重復加鎖。
Java API 描述
一個可重入的互斥鎖 Lock,它具有與使用 synchronized 方法和語句所訪問的隱式監視器鎖相同的一些基本行為和語義,但功能更強大。ReentrantLock 將由最近成功獲得鎖,并且還沒有釋放該鎖的線程所擁有。當鎖沒有被另一個線程所擁有時,調用 lock 的線程將成功獲取該鎖并返回。如果當前線程已經擁有該鎖,此方法將立即返回。可以使用 isHeldByCurrentThread() 和 getHoldCount() 方法來檢查此情況是否發生。
ReentrantLock還提供了公平鎖和非公平鎖的選擇, 其構造方法接受一個公平參數(默認是非公平方式),當傳入ture時表示公平鎖, 否則為非公平鎖。其兩者的主要區別在于公平鎖獲取鎖是有順序的。但是其效率往往沒有非公平鎖的效率高,在多線程的訪問時往往表現很低的吞吐量(即速度慢,常常急慢)。
來張圖緩解下
我們先來看一段代碼
ReentrantLock lock = new ReentrantLock(); try { lock.lock(); // 業務代碼 } finally { lock.unlock(); }
這一段代碼相信學過Java的同學都非常熟悉了,今天我們就以此為入口一步一步的帶你深入其底層世界。
// ReentrantLock --> lokc() 實現Lock 接口的方法 public void lock() { // 調用內部類sync 的lock方法, 這里有兩種實現,公平鎖(FairSync)非公平鎖(NonfairSync)這里我們來主要說 NonfairSync sync.lock(); }
ReentrantLock 的lock 方法, sync 為ReentrantLock的一個內部類,其繼承了AbstractQueuedSynchronizer(AQS), 他有兩個子類公平鎖FairSync 和非公平鎖NonfairSync
ReentrantLock 中其中大部分的功能的實現都是委托給內部類Sync實現的,在Sync 中定義了abstract void lock() 留給子類去實現, 默認實現了final boolean nonfairTryAcquire(int acquires) 方法,可以看出其為非公平鎖默認實現方式,下面我講下給看下非公平鎖lock方法。
// ReentrantLock$NonfairSync final void lock() { if (compareAndSetState(0, 1)) // 非公平原則, 上來就插隊來嘗試下獲取共享狀態,如果成功則設置當前持有鎖線程為自己,獲取鎖成功。 setExclusiveOwnerThread(Thread.currentThread()); else //如果失敗則調用AQS中的acquire方法 acquire(1); }
首先就嘗試獲取同步狀態(體現非公平鎖上來就插隊)如果成功則將持有鎖線程設置為自己,失敗則走AQS中的acquire方法。
// AQS中的acquire方法,在AQS中已經講過,首先會調用tryAcquire(arg)方法,tryAcquire(arg)方法會有具體由子類去實現。 public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }
這里AQS中的源碼我就不再過多的講解了(無非就是嘗試獲取同步狀態成功直接返回,失敗加入同步隊列等待被喚醒),主要來將留給子類實現的tryAcquire(arg)方法。
如有對AQS不明白的請看文章頭中列出的幾篇文章過一下或者鎖搜引擎中鎖搜下。
protected final boolean tryAcquire(int acquires) { // 非公平鎖的tryAcquire(arg)實現,委托給Sync.nonfairTryAcquire(int acquires)具體處理 return nonfairTryAcquire(acquires); }
ReentrantLock中非公平鎖tryAcquire(int acquires)的實現,具體調用其父類Sync中默認實現的(上面已經提過)。
final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); // 獲取共享狀態 int c = getState(); if (c == 0) { // 如果共享狀態為0,說明鎖空閑,利用CAS來獲取鎖(將共享狀態值改為1) if (compareAndSetState(0, acquires)) { // 如果設置成功,則表明獲取鎖成功,將持有鎖線程設置為自己 setExclusiveOwnerThread(current); return true; } } // 如果c != 0 則說明鎖已經被線程持有,判斷持有鎖的線程是不是自己(這里就是可重入鎖的具體體現) else if (current == getExclusiveOwnerThread()) { // 如果當前持有鎖的線程是自己,說明可重入,將共享狀態值加1,返回ture int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }
主要邏輯:
首先判斷同步狀態 state == 0 ?,
如果state == 0 則說明該鎖處于空閑狀態,直接通過CAS設置同步狀態,成功將持有鎖線程設置為自己返回ture,
如果state !=0 判斷鎖的持有者是否是自己,是則說明可重入將state 值加1 返回ture,
否則返回false.
來張圖加深下理解
注:此圖只是體現了RenntrantLock中的狀態,其中涉及到AQS中的狀態流轉沒有在這體現。
鎖的釋放邏輯就比較簡單
public void unlock() { sync.release(1); }
同樣在ReentrantLock.unlock()方法中將具體釋放邏輯委托給了內部類Sync來實現, 在這Sync 同樣沒有去實現release(1)而是使用其父類AQS的默認實現。
// 調用AQS中的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; }
AQS釋放鎖的邏輯比較簡單,同樣就不解釋了(無非就是釋放鎖,喚醒后繼節點)具體來看下需要自類實現的tryRelease(arg) 釋放共享狀態的方法。
protected final boolean tryRelease(int releases) { // 共享狀態值減去releases int c = getState() - releases; // 如果持有鎖的線程線程不是自己,則拋出異常(很好理解不能把別人的鎖釋放了) if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; // 共享狀態 state = 0 則表明釋放鎖成功 if (c == 0) { free = true; setExclusiveOwnerThread(null); } setState(c); return free; }
釋放共享狀態(鎖)的邏輯比較簡單,主要是將共享狀態的值減去releases,減后共享狀態值為0表示釋放鎖成功將持有鎖線程設置為null 返回 ture。
最后我們來對ReentrantLock加鎖和釋放鎖做個簡單總結,ReentrantLock 是一個可重入鎖提供了兩種實現方式公平鎖和非公平鎖。
非公平鎖獲取鎖流程:
1: 首先不管三七二一就來個 CAS 嘗試獲取鎖。
2: 成功則皆大歡喜。
3: 失敗,再次獲取下共享狀態(萬一這會有人釋放了尼)判斷是否為0
4: 如果為0 則說明鎖空閑,再次CAS獲取鎖成功將持有鎖線程設置為自己并返回ture
5:不為0,判斷持有者是否是自己、是自己表明可重入state + 1 返回ture 否則返回false(就去同步隊列中排隊去)。
非公平鎖釋放鎖流程
很簡單state - 1 = 0 則釋放成功否則失敗。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/74316.html
摘要:接著線程過來通過方式獲取鎖,獲取鎖的過程就是通過操作變量將其值從變為。線程加鎖成功后還有一步重要的操作,就是將設置成為自己。線程屁顛屁顛的就去等待區小憩一會去了。 一、寫在前面 這篇文章,我們聊一聊Java并發中的核武器, AQS底層實現。 不管是工作三四年、還是五六年的在工作或者面試中涉及到并發的是時候總是繞不過AQS這個詞。 首先,確實還有很多人連AQS是什么都不知道,甚至有的竟...
摘要:公平鎖阻塞隊列前邊有線程,要去后邊排隊,簡單來說滾后邊等著去。非公平鎖不管是否有線程排隊,先槍鎖基于實現的可重入鎖實現類。 AQS原理介紹: AQS (Abstra...
摘要:所以就有了讀寫鎖。只要沒有,讀取鎖可以由多個線程同時保持。其讀寫鎖為兩個內部類都實現了接口。讀寫鎖同樣依賴自定義同步器來實現同步狀態的,而讀寫狀態就是其自定義同步器的狀態。判斷申請寫鎖數量是否超標超標則直接異常,反之則設置共享狀態。 一、寫在前面 在上篇我們聊到了可重入鎖(排它鎖)ReentrantLcok ,具體參見《J.U.C|可重入鎖ReentrantLock》 Reentra...
摘要:所以就有了讀寫鎖。只要沒有,讀取鎖可以由多個線程同時保持。其讀寫鎖為兩個內部類都實現了接口。讀寫鎖同樣依賴自定義同步器來實現同步狀態的,而讀寫狀態就是其自定義同步器的狀態。判斷申請寫鎖數量是否超標超標則直接異常,反之則設置共享狀態。 一、寫在前面 在上篇我們聊到了可重入鎖(排它鎖)ReentrantLcok ,具體參見《J.U.C|可重入鎖ReentrantLock》 Reentra...
摘要:的主要功能和關鍵字一致,均是用于多線程的同步。而僅支持通過查詢當前線程是否持有鎖。由于和使用的是同一把可重入鎖,所以線程可以進入方法,并再次獲得鎖,而不會被阻塞住。公平與非公平公平與非公平指的是線程獲取鎖的方式。 1.簡介 可重入鎖ReentrantLock自 JDK 1.5 被引入,功能上與synchronized關鍵字類似。所謂的可重入是指,線程可對同一把鎖進行重復加鎖,而不會被阻...
閱讀 3234·2021-11-23 09:51
閱讀 1041·2021-08-05 09:58
閱讀 674·2019-08-29 16:05
閱讀 984·2019-08-28 18:17
閱讀 3038·2019-08-26 14:06
閱讀 2732·2019-08-26 12:20
閱讀 2167·2019-08-26 12:18
閱讀 3073·2019-08-26 11:56