淺析Java?ReentrantLock鎖的原理與使用
一. AQS內(nèi)部結(jié)構(gòu)介紹
JUC是Java中一個(gè)包 java.util.concurrent 。在這個(gè)包下,基本存放了Java中一些有關(guān)并發(fā)的類(lèi),包括并發(fā)工具,并發(fā)集合,鎖等。
AQS(抽象隊(duì)列同步器)是JUC下的一個(gè)基礎(chǔ)類(lèi),大多數(shù)的并發(fā)工具都是基于AQS實(shí)現(xiàn)的。
AQS本質(zhì)并沒(méi)有實(shí)現(xiàn)太多的業(yè)務(wù)功能,只是對(duì)外提供了三點(diǎn)核心內(nèi)容,來(lái)幫助實(shí)現(xiàn)其他的并發(fā)內(nèi)容。
三點(diǎn)核心內(nèi)容:
int state
- 比如ReentrantLock或者ReentrantReadWriteLock, 它們獲取鎖的方式,都是對(duì)state變量做修改實(shí)現(xiàn)的。
- 比如CountDownLatch基于state作為計(jì)數(shù)器,同樣的Semaphore也是用state記錄資源個(gè)數(shù)。
Node對(duì)象組成的雙向鏈表(AQS中)
比如ReentrantLock,有一個(gè)線(xiàn)程沒(méi)有拿到鎖資源,當(dāng)線(xiàn)程需要等待,則需要將線(xiàn)程封裝為Node對(duì)象,將Node添加到雙向鏈表,將線(xiàn)程掛起,等待即可。
Node對(duì)象組成的單向鏈表(AQS中的ConditionObject類(lèi)中)
比如ReentrantLock,一個(gè)線(xiàn)程持有鎖資源時(shí),執(zhí)行了await方法(類(lèi)比synchronized鎖執(zhí)行對(duì)象的wait方法),此時(shí)這個(gè)線(xiàn)程需要封裝為Node對(duì)象,并添加到單向鏈表。
二. Lock鎖和AQS關(guān)系
ReentrantLock就是基于AQS實(shí)現(xiàn)的。ReentrantLock類(lèi)中維護(hù)這個(gè)一個(gè)內(nèi)部抽象類(lèi)Sync,他繼承了AQS類(lèi)。ReentrantLock的lock和unlock方法就是調(diào)用的Sync的方法。
AQS流程(簡(jiǎn)述)
1. 當(dāng)new了一個(gè)ReentrantLock時(shí),AQS默認(rèn)state值為0, head 和 tail 都為null;
2. A線(xiàn)程執(zhí)行l(wèi)ock方法,獲取鎖資源。
3. A線(xiàn)程將state通過(guò)cas操作從0改為1,代表獲取鎖資源成功。
4. B線(xiàn)程要獲取鎖資源時(shí),鎖資源被A線(xiàn)程持有。
5. B線(xiàn)程獲取鎖資源失敗,需要添加到雙向鏈表中排隊(duì)。
6. 掛起B(yǎng)線(xiàn)程,等待A線(xiàn)程釋放鎖資源,再喚醒掛起的B線(xiàn)程。
7. A線(xiàn)程釋放鎖資源,將state從1改為0,再喚醒head.next節(jié)點(diǎn)。
8. B線(xiàn)程就可以重新嘗試獲取鎖資源。
注: 修改AQS雙向鏈表時(shí)要保證一個(gè)私有屬性變化和兩個(gè)共有屬性變化,只需要讓tail變化保證原子性即可。不能先改tail(會(huì)破壞雙向鏈表)
三. AQS - Lock鎖的tryAcquire方法
ReentrantLock中的lock方法實(shí)際是執(zhí)行的Sync的lock方法。
Sync是一個(gè)抽象類(lèi),繼承了AQS
Sync有兩個(gè)子類(lèi)實(shí)現(xiàn):
- FairSync: 公平鎖
- NonFairSync: 非公平鎖
Sync的lock方法實(shí)現(xiàn):
// 非公平鎖 final void lock() { // CAS操作,嘗試將state從0改為1 // 成功就拿到鎖資源, 失敗執(zhí)行acquire方法 if (compareAndSetState(0, 1)) // 成功就設(shè)置互斥鎖的為當(dāng)前線(xiàn)程擁有 setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); } // 公平鎖 final void lock() { acquire(1); }
如果CAS操作沒(méi)有成功,需要執(zhí)行acquire方法走后續(xù)
acquire方法是AQS提供的,公平和非公平都是走的這個(gè)方法
public final void acquire(int arg) { // 1. tryAcquire方法: 再次嘗試拿鎖 // 2. addWaiter方法: 沒(méi)有獲取到鎖資源,去排隊(duì) // 3. acquireQueued方法:掛起線(xiàn)程和后續(xù)被喚醒繼續(xù)獲取鎖資源的邏輯 if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // 如果這個(gè)過(guò)程中出現(xiàn)中斷,在整個(gè)過(guò)程結(jié)束后再自我中斷 selfInterrupt(); }
在AQS中tryAcquire是沒(méi)有具體實(shí)現(xiàn)邏輯的,AQS直接在tryAcquire方法中拋出異常
在公平鎖和非公平鎖中有自己的實(shí)現(xiàn)。
非公平鎖tryAcquire方法
// 非公平鎖 protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } // 非公平鎖再次嘗試拿鎖 (注:該方法屬于Sync類(lèi)中) final boolean nonfairTryAcquire(int acquires) { // 獲取當(dāng)前線(xiàn)程對(duì)象 final Thread current = Thread.currentThread(); // 獲取state狀態(tài) int c = getState(); // state是不是沒(méi)有線(xiàn)程持有鎖資源,可以嘗試獲取鎖 if (c == 0) { // 再次CAS操作嘗試修改state狀態(tài)從0改為1 if (compareAndSetState(0, acquires)) { // 成功就設(shè)置互斥鎖的為當(dāng)前線(xiàn)程擁有 setExclusiveOwnerThread(current); return true; } } // 鎖資源是否被當(dāng)前線(xiàn)程所持有 (可重入鎖) else if (current == getExclusiveOwnerThread()) { // 持有鎖資源為當(dāng)前, 則對(duì)state + 1 int nextc = c + acquires; // 健壯性判斷 if (nextc < 0) // overflow // 超過(guò)最大鎖重入次數(shù)會(huì)拋異常(幾率很小,理論上存在) throw new Error("Maximum lock count exceeded"); // 設(shè)置state狀態(tài),代表鎖重入成功 setState(nextc); return true; } return false; }
公平鎖tryAcquire方法
// 公平鎖 protected final boolean tryAcquire(int acquires) { // 獲取當(dāng)前線(xiàn)程對(duì)象 final Thread current = Thread.currentThread(); // 獲取state狀態(tài) int c = getState(); // state是不是沒(méi)有線(xiàn)程持有鎖資源 if (c == 0) { // 當(dāng)前鎖資源沒(méi)有被其他線(xiàn)程持有 // hasQueuedPredecessors方法: 鎖資源沒(méi)有被持有,進(jìn)入隊(duì)列排隊(duì) // 排隊(duì)規(guī)則: // 1. 檢查隊(duì)列沒(méi)有線(xiàn)程排隊(duì),搶鎖。 // 2. 檢查隊(duì)列有線(xiàn)程排隊(duì),查看當(dāng)前線(xiàn)程是否排在第一位,如果是搶鎖,否則入隊(duì)列(注:該方法只是判斷,沒(méi)有真正入隊(duì)列) if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { // 再次CAS操作嘗試, 成功就設(shè)置互斥鎖的為當(dāng)前線(xiàn)程擁有 setExclusiveOwnerThread(current); return true; } } // 鎖資源是否被當(dāng)前線(xiàn)程所持有 (可重入鎖) else if (current == getExclusiveOwnerThread()) { // 持有鎖資源為當(dāng)前, 則對(duì)state + 1 int nextc = c + acquires; // 健壯性判斷 if (nextc < 0) // 超過(guò)最大鎖重入次數(shù)會(huì)拋異常(幾率很小,理論上存在) throw new Error("Maximum lock count exceeded"); // 設(shè)置state狀態(tài),代表鎖重入成功 setState(nextc); return true; } return false; }
四. AQS的addWaiter方法
addWaiter方法,就是將當(dāng)前線(xiàn)程封裝為Node對(duì)象,并且插入到AQS的雙向鏈表。
// 線(xiàn)程入隊(duì)列排隊(duì) private Node addWaiter(Node mode) { // 將當(dāng)前對(duì)象封裝為Node對(duì)象 // Node.EXCLUSIVE 表示互斥 Node.SHARED 表示共享 Node node = new Node(Thread.currentThread(), mode); // 獲取tail節(jié)點(diǎn) Node pred = tail; // 判斷雙向鏈表隊(duì)列有沒(méi)有初始化 if (pred != null) { // 將當(dāng)前線(xiàn)程封裝的Node節(jié)點(diǎn)prev屬性指向tail尾節(jié)點(diǎn) node.prev = pred; // 通過(guò)CAS操作設(shè)置當(dāng)前線(xiàn)程封裝的Node節(jié)點(diǎn)為尾節(jié)點(diǎn) if (compareAndSetTail(pred, node)) { // 成功則將上一個(gè)尾節(jié)點(diǎn)的next屬性指向當(dāng)前線(xiàn)程封裝的Node節(jié)點(diǎn) pred.next = node; return node; } } // 沒(méi)有初始化head 和 tail 都等于null // enq方法: 插入雙向鏈表和初始化雙向鏈表 enq(node); // 完成節(jié)點(diǎn)插入 return node; } // 插入雙向鏈表和初始化雙向鏈表 private Node enq(final Node node) { // 死循環(huán) for (;;) { // 獲取當(dāng)前tail節(jié)點(diǎn) Node t = tail; // 判斷尾節(jié)點(diǎn)是否初始 if (t == null) { // Must initialize // 通過(guò)CAS操作初始化初始化一個(gè)虛擬的Node節(jié)點(diǎn),賦給head節(jié)點(diǎn) if (compareAndSetHead(new Node())) tail = head; } else { // 完成當(dāng)前線(xiàn)程N(yùn)ode節(jié)點(diǎn)加入AQS雙向鏈表的過(guò)程 // 當(dāng)前線(xiàn)程封裝的Node的上一個(gè)prev屬性指向tail節(jié)點(diǎn) // 流程: 1. prev(私有) ---> 2. tail(共有) ---> 3. next (共有) node.prev = t; // 通過(guò)CAS操作修改tail尾節(jié)點(diǎn)指向當(dāng)前線(xiàn)程封裝的Node if (compareAndSetTail(t, node)) { // 將當(dāng)前線(xiàn)程封裝的Node節(jié)點(diǎn)賦給上一個(gè)Node的下一個(gè)next屬性 t.next = node; return t; } } } }
五. AQS的acquireQueued方法
acquireQueued方法主要就是線(xiàn)程掛起以及重新嘗試獲取鎖資源的地方
重新獲取鎖資源主要有兩種情況:
- 上來(lái)就排在head.next,就回去嘗試拿鎖
- 喚醒之后嘗試拿鎖
// 當(dāng)前線(xiàn)程N(yùn)ode添加到AQS隊(duì)列后續(xù)操作 final boolean acquireQueued(final Node node, int arg) { // 標(biāo)記,記錄拿鎖狀態(tài) 失敗 boolean failed = true; try { // 中斷狀態(tài) boolean interrupted = false; // 死循環(huán) for (;;) { // 獲取當(dāng)前節(jié)點(diǎn)的上一個(gè)節(jié)點(diǎn) prev final Node p = node.predecessor(); // 判斷當(dāng)前節(jié)點(diǎn)是否是head,是則代表當(dāng)前節(jié)點(diǎn)排在第一位 // 如果是第一位,執(zhí)行tryAcquire方法嘗試拿鎖 if (p == head && tryAcquire(arg)) { // 都成功,代表拿到鎖資源 // 將當(dāng)前線(xiàn)程N(yùn)ode設(shè)置為head節(jié)點(diǎn),同時(shí)將Node的thread 和 prev屬性設(shè)置為null setHead(node); // 將上一個(gè)head的next屬性設(shè)置為null,等待GC回收 p.next = null; // help GC // 拿鎖狀態(tài) 成功 failed = false; // 返回中斷狀態(tài) return interrupted; } // 沒(méi)有獲取到鎖 --- 嘗試掛起線(xiàn)程 // shouldParkAfterFailedAcquire方法: 掛起線(xiàn)程前的準(zhǔn)備 // parkAndCheckInterrupt方法: 掛起當(dāng)前線(xiàn)程 if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) // 設(shè)置中斷線(xiàn)程狀態(tài) interrupted = true; } } finally { // 取消節(jié)點(diǎn) if (failed) cancelAcquire(node); } } // 檢查并更新無(wú)法獲取鎖節(jié)點(diǎn)的狀態(tài) private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { // 獲取上一個(gè)節(jié)點(diǎn)的ws狀態(tài) /** * SIGNAL(-1) 表示當(dāng)前節(jié)點(diǎn)釋放鎖的時(shí)候,需要喚醒下一個(gè)節(jié)點(diǎn)?;蛘哒f(shuō)后繼節(jié)點(diǎn)在等待當(dāng)前節(jié)點(diǎn)喚醒,后繼節(jié)點(diǎn)入隊(duì)時(shí)候,會(huì)將前驅(qū)節(jié)點(diǎn)更新給signal。 * CANCELLED(1) 表示當(dāng)前節(jié)點(diǎn)已取消調(diào)度。當(dāng)timeout或者中斷情況下,會(huì)觸發(fā)變更為此狀態(tài),進(jìn)入該狀態(tài)后的節(jié)點(diǎn)不再變化。 * CONDITION(-2) 當(dāng)其他線(xiàn)程調(diào)用了condition的signal方法后,condition狀態(tài)的節(jié)點(diǎn)會(huì)從等待隊(duì)列轉(zhuǎn)移到同步隊(duì)列中,等待獲取同步鎖。 * PROPAGATE(-3) 表示共享模式下,前驅(qū)節(jié)點(diǎn)不僅會(huì)喚醒其后繼節(jié)點(diǎn),同時(shí)也可能喚醒后繼的后繼節(jié)點(diǎn)。 * 默認(rèn)(0) 新節(jié)點(diǎn)入隊(duì)時(shí)候的默認(rèn)狀態(tài)。 */ int ws = pred.waitStatus; // 判斷上個(gè)節(jié)點(diǎn)ws狀態(tài)是否是 -1, 是則掛起 if (ws == Node.SIGNAL) return true; if (ws > 0) { /** * 判斷上個(gè)節(jié)點(diǎn)是否是取消或者其他狀態(tài)。 * 向前找到不是取消狀態(tài)的節(jié)點(diǎn),修改ws狀態(tài)。 * 注意:那些放棄的結(jié)點(diǎn),由于被自己“加塞”到它們前邊,它們相當(dāng)于形成一個(gè)無(wú)引用鏈, * 稍后就會(huì)被GC回收,這個(gè)操作實(shí)際是把隊(duì)列中的cancelled節(jié)點(diǎn)剔除掉。 */ do { node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } else { // 如果前驅(qū)節(jié)點(diǎn)正常,那就把上一個(gè)節(jié)點(diǎn)的狀態(tài)通過(guò)CAS的方式設(shè)置成-1 compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; } // 掛起當(dāng)前線(xiàn)程 private final boolean parkAndCheckInterrupt() { // 掛起當(dāng)前線(xiàn)程 LockSupport.park(this); // 返回中斷標(biāo)志 return Thread.interrupted(); }
六. AQS的Lock鎖的release方法
// 互斥鎖模式 解鎖 public final boolean release(int arg) { // 嘗試是否可以解鎖 if (tryRelease(arg)) { Node h = head; // 判斷雙鏈表是否存在線(xiàn)程排隊(duì) if (h != null && h.waitStatus != 0) // 喚醒后續(xù)線(xiàn)程 unparkSuccessor(h); return true; } return false; } // 嘗試是否可以解鎖 protected final boolean tryRelease(int releases) { // 鎖狀態(tài) = 狀態(tài) - 1 int c = getState() - releases; // 判斷鎖是是否是當(dāng)前線(xiàn)程持有 if (Thread.currentThread() != getExclusiveOwnerThread()) // 當(dāng)前線(xiàn)程沒(méi)有持有拋出異常 throw new IllegalMonitorStateException(); boolean free = false; // 當(dāng)前鎖狀態(tài)變?yōu)?,則清空鎖歸屬線(xiàn)程 if (c == 0) { free = true; setExclusiveOwnerThread(null); } // 設(shè)置鎖狀態(tài)為0 setState(c); return free; } // 喚醒線(xiàn)程 private void unparkSuccessor(Node node) { // 獲取頭節(jié)點(diǎn)的狀態(tài) int ws = node.waitStatus; if (ws < 0) // 通過(guò)CAS將頭節(jié)點(diǎn)的狀態(tài)設(shè)置為初始狀態(tài) compareAndSetWaitStatus(node, ws, 0); // 后繼節(jié)點(diǎn) Node s = node.next; if (s == null || s.waitStatus > 0) { s = null; // 從尾節(jié)點(diǎn)開(kāi)始往前遍歷,尋找離頭節(jié)點(diǎn)最近的等待狀態(tài)正常的節(jié)點(diǎn) for (Node t = tail; t != null && t != node; t = t.prev) if (t.waitStatus <= 0) s = t; } if (s != null) // 真正的喚醒操作 LockSupport.unpark(s.thread); }
到此這篇關(guān)于淺析Java ReentrantLock鎖的原理與使用的文章就介紹到這了,更多相關(guān)Java ReentrantLock鎖內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
通過(guò)spring boot 設(shè)置tomcat解決 post參數(shù)限制問(wèn)題
這篇文章主要介紹了通過(guò)spring boot 設(shè)置tomcat解決 post參數(shù)限制問(wèn)題,需要的朋友可以參考下2019-05-05MyBatis-Plus自定義SQL和復(fù)雜查詢(xún)的實(shí)現(xiàn)
MyBatis-Plus增強(qiáng)了MyBatis的功能,提供注解和XML兩種自定義SQL方式,支持復(fù)雜查詢(xún)?nèi)缍啾黻P(guān)聯(lián)、動(dòng)態(tài)分頁(yè)等,通過(guò)注解如@Select、@Insert、@Update、@Delete實(shí)現(xiàn)CRUD操作,本文就來(lái)介紹一下,感興趣的可以了解一下2024-10-10Spring Boot集成MinIO進(jìn)行文件存儲(chǔ)和管理的詳細(xì)步驟
這篇文章主要介紹了Spring Boot集成MinIO進(jìn)行文件存儲(chǔ)和管理的詳細(xì)步驟,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),感興趣的朋友一起看看吧2025-04-04Springboot項(xiàng)目使用Slf4j將日志保存到本地目錄的實(shí)現(xiàn)代碼
這篇文章主要介紹了Springboot項(xiàng)目使用Slf4j將日志保存到本地目錄的實(shí)現(xiàn)方法,本文通過(guò)示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-05-05Java比較兩個(gè)對(duì)象中全部屬性值是否相等的方法
本文主要介紹了Java比較兩個(gè)對(duì)象中全部屬性值是否相等的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-08-08一文詳解SpringBoot如何優(yōu)雅地實(shí)現(xiàn)異步調(diào)用
SpringBoot想必大家都用過(guò),但是大家平時(shí)使用發(fā)布的接口大都是同步的,那么你知道如何優(yōu)雅的實(shí)現(xiàn)異步呢?這篇文章就來(lái)和大家詳細(xì)聊聊2023-03-03聊聊springboot靜態(tài)資源加載的規(guī)則
這篇文章主要介紹了springboot靜態(tài)資源加載的規(guī)則,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12解決 Spring RestTemplate post傳遞參數(shù)時(shí)報(bào)錯(cuò)問(wèn)題
本文詳解說(shuō)明了RestTemplate post傳遞參數(shù)時(shí)報(bào)錯(cuò)的問(wèn)題及其原由,需要的朋友可以參考下2020-02-02Mybatis Select Count(*)的返回值類(lèi)型介紹
這篇文章主要介紹了Mybatis Select Count(*)的返回值類(lèi)型,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12