java鎖機(jī)制ReentrantLock源碼實(shí)例分析
一:簡(jiǎn)述
ReentrantLock是java.util.concurrent包中提供的一種鎖機(jī)制,它是一種可重入,互斥的鎖,ReentrantLock還同時(shí)支持公平和非公平兩種實(shí)現(xiàn)。本篇文章基于java8,對(duì)并發(fā)工具ReentrantLock的實(shí)現(xiàn)原理進(jìn)行分析。
二:ReentrantLock類圖
三:流程簡(jiǎn)圖
四:源碼分析
我們以lock()方法和unlock()方法為入口對(duì)ReentrantLock的源碼進(jìn)行分析。
lock()源碼分析:
ReentrantLock在構(gòu)造構(gòu)造方法創(chuàng)建對(duì)象的時(shí)候會(huì)根據(jù)構(gòu)造函數(shù)傳遞的fair參數(shù) 創(chuàng)建不同的Sync對(duì)象(默認(rèn)是非公平鎖的實(shí)現(xiàn)),reentrantLock.lock()會(huì)調(diào)用sync.lock()。在Sync類中的lock()方法是一個(gè)抽象方法,具體實(shí)現(xiàn)分別在FairSync(公平)和NonfairSync(非公平)中。
public ReentrantLock() { //默認(rèn)是非公平鎖 sync = new NonfairSync(); } public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }
public void lock() { sync.lock(); }
非公平實(shí)現(xiàn):
final void lock() { // state表示鎖重入次數(shù) 0代表無鎖狀態(tài) //嘗試搶占鎖一次 利用cas替換state標(biāo)志 替換成功代表?yè)屨兼i成功 if (compareAndSetState(0, 1)) //搶占成功將搶占到鎖的線程設(shè)置為當(dāng)前線程 setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); }
公平鎖實(shí)現(xiàn):
final void lock() { acquire(1); }
可以看出非公平鎖在lock()方法開始會(huì)嘗試cas搶占一次鎖,也就是在這里會(huì)插隊(duì)一次。然后都會(huì)調(diào)用acquire()方法。acquire()方法中調(diào)用了三個(gè)方法,tryAcquire(),addWaiter(),acquireQueued()三個(gè)方法,而這三個(gè)方法正是ReentrantLock加鎖的核心方法,接下來我們會(huì)對(duì)這三個(gè)方法進(jìn)行重點(diǎn)的分析。
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) //這里acquireQueued()是一步步將線程是否中斷的標(biāo)志傳回來的 如果為true 那么代表線程被中斷過 需要設(shè)置中斷標(biāo)志 交給客戶端處理 // 因?yàn)長(zhǎng)ockSupport.park()之后不會(huì)對(duì)中斷進(jìn)行響應(yīng) 所以需要一步一步將中斷標(biāo)記傳回來 selfInterrupt(); }
tryAcquire()方法
流程圖:
tryAcquire()方法是搶占鎖的方法,返回ture表示線程搶占到鎖了,如果搶占到鎖就什么都不做,直接執(zhí)行同步代碼,如果沒有搶占到鎖就需要將線程的信息保存起來,并且阻塞線程,也就是調(diào)用addWaiter()方法和acquireQueued()方法。
tryAcquire()也有公平和非公平鎖兩種實(shí)現(xiàn)。
公平鎖實(shí)現(xiàn):
protected final boolean tryAcquire(int acquires) { //獲取當(dāng)前線程 final Thread current = Thread.currentThread(); //獲取state的值 int c = getState(); if (c == 0) { //如果state為0 代表現(xiàn)在是無鎖狀態(tài) 那么可以搶占鎖 //公平鎖這里多了一個(gè)判斷 只有在AQS鏈表中不存在元素 才去嘗試搶占鎖 否則就去鏈表中排隊(duì) if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { //cas替換成功 那么就代表?yè)屨兼i成功了 將獲取鎖的線程設(shè)置為當(dāng)前線程 setExclusiveOwnerThread(current); return true; } } //判斷獲取鎖的線程是否是當(dāng)前線程 如果是的話增加重入次數(shù) (即增加state的值) else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; } }
非公平鎖實(shí)現(xiàn):
protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); }
final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { //如果state為0 代表現(xiàn)在是無鎖狀態(tài) 非公平鎖這里就直接嘗試搶占鎖 // 公平鎖這里多了一個(gè)判斷 先去看AQS鏈表中有沒有已經(jīng)在等待的線程 有的話就不會(huì)嘗試去搶占鎖了,非公平鎖這里沒有這個(gè)判斷,也就是這里允許插隊(duì)(不公平) if (compareAndSetState(0, acquires)) { //cas替換成功 那么就代表?yè)屨兼i成功了 將獲取鎖的線程設(shè)置為當(dāng)前線程 setExclusiveOwnerThread(current); return true; } } //判斷獲取鎖的線程是否是當(dāng)前線程 如果是的話增加重入次數(shù) (即增加state的值) else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); //重新設(shè)置state的值 setState(nextc); return true; } return false; }
接下來分析addWaiter()方法
addWaiter()
addWaiter()方法的作用就是將沒有獲取到鎖的線程封裝成一個(gè)Node對(duì)象,然后存儲(chǔ)在AbstractQueuedSynchronizer這個(gè)隊(duì)列同步器中(為了偷懶簡(jiǎn)稱AQS)。我們先看一下Node對(duì)象的結(jié)構(gòu)。
static final class Node { static final Node SHARED = new Node(); static final Node EXCLUSIVE = null; static final int CANCELLED = 1; static final int SIGNAL = -1; static final int CONDITION = -2; static final int PROPAGATE = -3; volatile int waitStatus; volatile Node prev; volatile Node next; volatile Thread thread; Node nextWaiter; final boolean isShared() { return nextWaiter == SHARED; } final Node predecessor() throws NullPointerException { Node p = prev; if (p == null) throw new NullPointerException(); else return p; } Node() { // Used to establish initial head or SHARED marker } Node(Thread thread, Node mode) { // Used by addWaiter this.nextWaiter = mode; this.thread = thread; } Node(Thread thread, int waitStatus) { // Used by Condition this.waitStatus = waitStatus; this.thread = thread; } }
通過Node對(duì)象的結(jié)構(gòu)我們可以看出這是一個(gè)雙向鏈表的結(jié)構(gòu),保存了prev和next引用,thread成員變量用來保存阻塞的線程引用,并且node是有狀態(tài)的,分別是CANCELLED,SIGNAL,CONDITION,PROPAGATE,其中ReentrantLock涉及到的狀態(tài)就是SIGNAL(等待被喚醒),CANCELLED(取消)(由于相關(guān)性不強(qiáng),其他的狀態(tài)在后續(xù)文章用到的時(shí)候在講吧)。而AQS又保存了鏈表的頭結(jié)點(diǎn)head和尾結(jié)點(diǎn)tail,所以實(shí)際上AQS存儲(chǔ)阻塞線程的數(shù)據(jù)結(jié)構(gòu)是一 個(gè)Node雙向鏈表。addWaiter()方法的作用就是將阻塞線程封裝成Node并且將其保存在AQS的鏈表結(jié)構(gòu)中。
流程圖:
源碼分析:
private Node addWaiter(Node mode) { //將沒有獲得鎖的線程封裝成一個(gè)node Node node = new Node(Thread.currentThread(), mode); // Try the fast path of enq; backup to full enq on failure Node pred = tail; //如果AQS尾結(jié)點(diǎn)不為null 代表AQS鏈表已經(jīng)初始化 嘗試將構(gòu)建好的節(jié)點(diǎn)添加到鏈表的尾部 if (pred != null) { node.prev = pred; //cas替換AQS的尾結(jié)點(diǎn) if (compareAndSetTail(pred, node)) { pred.next = node; return node; } } //沒有初始化調(diào)用enq()方法 enq(node); return node; }
private Node enq(final Node node) { //自旋 for (;;) { Node t = tail; //尾結(jié)點(diǎn)為空 說明AQS鏈表還沒有初始化 那么進(jìn)行初始化 if (t == null) { // Must initialize //cas 將AQS的head節(jié)點(diǎn) 初始化 成功初始化head之后,將尾結(jié)點(diǎn)也初始化 //注意 這里我們可以看到head節(jié)點(diǎn)是不存儲(chǔ)線程信息的 也就是說head節(jié)點(diǎn)相當(dāng)于是一個(gè)虛擬節(jié)點(diǎn) if (compareAndSetHead(new Node())) tail = head; } else { //尾結(jié)點(diǎn)不為空 那么直接添加到鏈表的尾部即可 node.prev = t; if (compareAndSetTail(t, node)) { t.next = node; return t; } } } }
接下來分析acquireQueued()
acquireQueued()
acquireQueued()方法的作用就是利用Locksupport.park()方法將AQS鏈表中存儲(chǔ)的線程阻塞起來。
流程圖:
源碼分析:
final boolean acquireQueued(final Node node, int arg) { boolean failed = true; try { boolean interrupted = false; //進(jìn)入自旋 for (;;) { //獲取當(dāng)前節(jié)點(diǎn)的前一個(gè)節(jié)點(diǎn) final Node p = node.predecessor(); // 如果前一個(gè)節(jié)點(diǎn)是head 而且再次嘗試獲取鎖成功,將節(jié)點(diǎn)從AQS隊(duì)列中去除 并替換head 同時(shí)返回中斷標(biāo)志 if (p == head && tryAcquire(arg)) { setHead(node); p.next = null; // help GC failed = false; //注意 只有搶占到鎖才會(huì)跳出這個(gè)for循環(huán) return interrupted; } //去除狀態(tài)為CANCELLED的節(jié)點(diǎn) 并且阻塞線程 線程被阻塞在這 //注意 線程被喚醒之后繼續(xù)執(zhí)行這個(gè)for循環(huán) 嘗試搶占鎖 沒有搶占到的話又會(huì)阻塞 if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) //如果失敗 將node狀態(tài)修改為CANCELLED cancelAcquire(node); } }
在acquireQueued()方法中有兩個(gè)方法比較重要shouldParkAfterFailedAcquire()方法和parkAndCheckInterrupt()方法。
shouldParkAfterFailedAcquire()
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { int ws = pred.waitStatus; if (ws == Node.SIGNAL) //如果節(jié)點(diǎn)是SIGNAL狀態(tài) 不需要處理 直接返回 return true; if (ws > 0) { /* * Predecessor was cancelled. Skip over predecessors and * indicate retry. */ //如果節(jié)點(diǎn)狀態(tài)>0 說明節(jié)點(diǎn)是取消狀態(tài) 這種狀態(tài)的節(jié)點(diǎn)需要被清除 用do while循環(huán)順便清除一下前面的連續(xù)的、狀態(tài)為取消的節(jié)點(diǎn) do { node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } else { /* * waitStatus must be 0 or PROPAGATE. Indicate that we * need a signal, but don't park yet. Caller will need to * retry to make sure it cannot acquire before parking. */ //正常的情況下 利用cas將前一個(gè)節(jié)點(diǎn)的狀態(tài)替換為 SIGNAL狀態(tài) 也就是-1 //注意 這樣隊(duì)列中節(jié)點(diǎn)的狀態(tài) 除了最后一個(gè)都是-1 包括head節(jié)點(diǎn) compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; }
parkAndCheckInterrupt()
private final boolean parkAndCheckInterrupt() { //掛起當(dāng)前線程 并且返回中斷標(biāo)志 LockSupport.park(thread) 會(huì)調(diào)用UNSAFE.park()方法將線程阻塞起來(是一個(gè)native方法) LockSupport.park(this); return Thread.interrupted(); }
到這里lock()方法也就分析完了 接下來我們分析unlock()方法
unlock()方法源碼分析:
流程圖:
reentrantLock的unlock()方法會(huì)調(diào)用sync的release()方法。
public void unlock() { //每次調(diào)用unlock 將state減1 sync.release(1); }
release()方法有兩個(gè)方法比較重要,tryRelease()方法和unparkSuccessor(),tryRelease()方法計(jì)算state的值,看線程是否已經(jīng)徹底釋放鎖(這是因?yàn)镽eentrantLock是支持重入的),如果已經(jīng)徹底釋放鎖那么需要調(diào)用unparkSuccessor()方法來喚醒線程,否則不需要喚醒線程。
public final boolean release(int arg) { //只有tryRelease返回true 說明已經(jīng)釋放鎖 需要將阻塞的線程喚醒 否則不需要喚醒別的線程 if (tryRelease(arg)) { Node h = head; //如果頭結(jié)點(diǎn)不為空 而且狀態(tài)不為0 代表同步隊(duì)列已經(jīng)初始化 且存在需要喚醒的node //注意 同步隊(duì)列的頭結(jié)點(diǎn)相當(dāng)于是一個(gè)虛擬節(jié)點(diǎn) 這一點(diǎn)我們可以在構(gòu)建節(jié)點(diǎn)的代碼中很清楚的知道 //并且在shouldParkAfterFailedAcquire方法中 會(huì)把head節(jié)點(diǎn)的狀態(tài)修改為-1 //如果head的狀態(tài)為0 那么代表隊(duì)列中沒有需要被喚醒的元素 直接返回true if (h != null && h.waitStatus != 0) //喚醒頭結(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn) unparkSuccessor(h); return true; } return false; }
tryRelease()
protected final boolean tryRelease(int releases) { //減少重入次數(shù) int c = getState() - releases; //如果獲取鎖的線程不是當(dāng)前線程 拋出異常 if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; //如果state為0 說明已經(jīng)徹底釋放了鎖 返回true if (c == 0) { free = true; //將獲取鎖的線程設(shè)置為null setExclusiveOwnerThread(null); } //重置state的值 setState(c); //如果釋放了鎖 就返回true 否則返回false return free; }
unparkSuccessor()
private void unparkSuccessor(Node node) { //獲取頭結(jié)點(diǎn)的狀態(tài) 將頭結(jié)點(diǎn)狀態(tài)設(shè)置為0 代表現(xiàn)在正在有線程被喚醒 如果head狀態(tài)為0 就不會(huì)進(jìn)入這個(gè)方法了 int ws = node.waitStatus; if (ws < 0) //將頭結(jié)點(diǎn)狀態(tài)設(shè)置為0 compareAndSetWaitStatus(node, ws, 0); /* * Thread to unpark is held in successor, which is normally * just the next node. But if cancelled or apparently null, * traverse backwards from tail to find the actual * non-cancelled successor. */ //喚醒頭結(jié)點(diǎn)的下一個(gè)狀態(tài)不是cancelled的節(jié)點(diǎn) (因?yàn)轭^結(jié)點(diǎn)是不存儲(chǔ)阻塞線程的) Node s = node.next; //當(dāng)前節(jié)點(diǎn)是null 或者是cancelled狀態(tài) if (s == null || s.waitStatus > 0) { s = null; //從aqs鏈表的尾部開始遍歷 找到離頭結(jié)點(diǎn)最近的 不為空的 狀態(tài)不是cancelled的節(jié)點(diǎn) 賦值給s 這里為什么從尾結(jié)點(diǎn)開始遍歷而是頭結(jié)點(diǎn) 應(yīng)該是因?yàn)樘砑咏Y(jié)點(diǎn)的時(shí)候是先初始化結(jié)點(diǎn)的prev的, 從尾結(jié)點(diǎn)開始遍歷 不會(huì)出現(xiàn)prve沒有賦值的情況 for (Node t = tail; t != null && t != node; t = t.prev) if (t.waitStatus <= 0) s = t; } if (s != null) //調(diào)用LockSupport.unpark()喚醒指定的線程 LockSupport.unpark(s.thread); }
五:總結(jié)
最后總結(jié)一下,有以下幾點(diǎn)需要注意:
1.ReentrantLock有公平鎖和非公平鎖兩種實(shí)現(xiàn),其實(shí)這兩種實(shí)現(xiàn)的差別只有兩點(diǎn),第一是在lock()方法開始的時(shí)候非公平鎖會(huì)嘗試cas搶占鎖插一次隊(duì), 第二是在tryAcquire()方法發(fā)現(xiàn)state為0的時(shí)候,非公平鎖會(huì)搶占一次鎖,而公平鎖會(huì)判斷AQS鏈表中是否存在等待的線程,沒有等待的線程才會(huì)去搶占鎖。
2.AQS存儲(chǔ)阻塞線程的數(shù)據(jù)結(jié)構(gòu)是一個(gè)雙向鏈表的結(jié)構(gòu),而且它是遵循先進(jìn)先出的,因?yàn)樗菑念^結(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)開始喚醒,而添加新的節(jié)點(diǎn)的時(shí)候是添加到鏈表的尾部,所以AQS同時(shí)也是一個(gè)隊(duì)列的數(shù)據(jù)結(jié)構(gòu)。
3.線程被喚醒之后會(huì)繼續(xù)執(zhí)行acquireQueued()方法,因?yàn)樗亲枞赼cquireQueued()方法的for循環(huán)中的,喚醒后嘗試去獲取鎖,獲取成功就會(huì)將節(jié)點(diǎn)從AQS中刪除并跳出for循環(huán),否則又會(huì)繼續(xù)阻塞。(獲取鎖失敗的原因就是因?yàn)橛腥瞬尻?duì)啊。。也就是非公平鎖導(dǎo)致的)。
以上就是java鎖機(jī)制ReentrantLock源碼實(shí)例分析的詳細(xì)內(nèi)容,更多關(guān)于java鎖機(jī)制ReentrantLock的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Intellij IDEA導(dǎo)入JAVA項(xiàng)目并啟動(dòng)(圖文教程)
這篇文章主要介紹了Intellij IDEA導(dǎo)入JAVA項(xiàng)目并啟動(dòng),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08解決idea找不到類could not find artifact問題
本文總結(jié)了解決Java項(xiàng)目中找不到類的問題的常見解決方案,包括刷新Maven項(xiàng)目、清理IDEA緩存、Maven Clean Install、重新Package、解決依賴沖突和手動(dòng)導(dǎo)入依賴包等方法2025-01-01SpringBoot實(shí)現(xiàn)多環(huán)境配置文件切換教程詳解
很多時(shí)候,我們項(xiàng)目在開發(fā)環(huán)境和生成環(huán)境的環(huán)境配置是不一樣的,例如,數(shù)據(jù)庫(kù)配置,這個(gè)時(shí)候就需要切換環(huán)境配置文件。本文將詳細(xì)講解SpringBoot如何切換配置文件,需要的可以參考一下2022-03-03利用Java的MyBatis框架獲取MySQL中插入記錄時(shí)的自增主鍵
這篇文章主要介紹了利用Java的MyBatis框架獲取MySQL中插入記錄的自增長(zhǎng)字段值,其中大家可以看到MyBatis支持普通SQL語句所帶來的遍歷,需要的朋友可以參考下2016-06-06總結(jié)一下Java回調(diào)機(jī)制的相關(guān)知識(shí)
今天給大家?guī)淼氖顷P(guān)于Java的相關(guān)知識(shí),文章圍繞著Java回調(diào)機(jī)制展開,文中有非常詳細(xì)的介紹及代碼示例,需要的朋友可以參考下2021-06-06Windows下apache ant安裝、環(huán)境變量配置教程
這篇文章主要介紹了Windows下apache ant安裝、環(huán)境變量配置教程,ANT的安裝很簡(jiǎn)單,本文同時(shí)講解了驗(yàn)證安裝是否成功的方法和使用方法實(shí)例,需要的朋友可以參考下2015-06-06詳解Java 網(wǎng)絡(luò)IO編程總結(jié)(BIO、NIO、AIO均含完整實(shí)例代碼)
本篇文章主要介紹了Java 網(wǎng)絡(luò)IO編程總結(jié)(BIO、NIO、AIO均含完整實(shí)例代碼),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-12-12