Java中的ReentrantLock原理解析
一、示例分析
公平鎖
/***
*說明: 該示例使用的是公平策略。
*/
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class MyThread extends Thread {
private Lock lock;
public MyThread(String name, Lock lock) {
super(name);
this.lock = lock;
}
public void run () {
lock.lock();
try {
System.out.println(Thread.currentThread() + " running");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
} finally {
lock.unlock();
}
}
}
public class AbstractQueuedSynchonizerDemo {
public static void main(String[] args) throws InterruptedException {
Lock lock = new ReentrantLock(true);
MyThread t1 = new MyThread("t1", lock);
MyThread t2 = new MyThread("t2", lock);
MyThread t3 = new MyThread("t3", lock);
t1.start();
t2.start();
t3.start();
}
}
//運(yùn)行結(jié)果:
Thread[t1,5,main] running
Thread[t2,5,main] running
Thread[t3,5,main] running
二、加鎖lock過程
- 獲取當(dāng)前線程
- 獲取鎖的狀態(tài)getState()
- 判斷鎖的狀態(tài)
- 如果鎖是自由狀態(tài)則第五步,如果不是自由狀態(tài)則第七步
- 判斷自己是否需要排隊(duì)
- 什么情況下當(dāng)前線程不需要排隊(duì)(排隊(duì)=入隊(duì)+阻塞)
- 隊(duì)列沒有初始 對頭和隊(duì)尾等于null的時候是不需要排隊(duì)
- 隊(duì)列當(dāng)中只有一個線程的時候是不需要排隊(duì)的
- 如果不需要排隊(duì)則cas加鎖
- 判斷是否重入(一般情況下不重入)
- 下不重入直接返回false(加鎖失?。?/li>
- 加鎖失敗之后會調(diào)用addWaiter,主要是入隊(duì)(入隊(duì)不等于排隊(duì))入隊(duì)完成之后第一個節(jié)點(diǎn)是一個虛擬出來的節(jié)點(diǎn)(thread等于null),即前置節(jié)點(diǎn),而不是我們?nèi)腙?duì)的節(jié)點(diǎn)
- 判斷是否需要自旋
- 如果需要自旋則再次獲取鎖,如果失敗則park
- 如果不需要自旋則直接park
線程執(zhí)行l(wèi)ock.lock,下圖給出了方法調(diào)用中的主要方法。

由上圖可知,最后的結(jié)果是t3線程會被禁止,因?yàn)檎{(diào)用了LockSupport.park。
三、加鎖總結(jié)
- AQS框架 第一個線程t1獲取鎖的時候 代價基本為0(cas鎖的狀態(tài),記錄當(dāng)前持有鎖的線程),而且連隊(duì)列都為null(隊(duì)列都沒有初始化)
- 當(dāng)?shù)谝粋€線程釋放鎖之后第二個線程t2來加鎖(t1 和t2是沒有競爭執(zhí)行),代價基本為0,(cas鎖的狀態(tài),記錄當(dāng)前持有鎖的線程),而且連隊(duì)列都為null(隊(duì)列都沒有初始化),因?yàn)槭侵貜?fù)上面步驟6
- t1沒有釋放鎖這個t2來加鎖,鎖的情況如下
- t2會加鎖失敗,則返回false,然后調(diào)用addWaiter方法區(qū)初始化隊(duì)列,然后自己入隊(duì)
- 會把t2封裝成為一個node,調(diào)用ENQ方法讓當(dāng)前node入隊(duì)
- 入隊(duì)成功之后調(diào)用acquireQueued final Node p = node.predecessor();獲取上一個節(jié)點(diǎn)
- 判斷上一個節(jié)點(diǎn)是否頭節(jié)點(diǎn)
- 如果是頭結(jié)點(diǎn)則再次獲取鎖(一次自旋)
- 如果獲取到了鎖,表示t1釋放了鎖(AQS框架當(dāng)中第一個node永遠(yuǎn)可以理解為是當(dāng)前持有鎖的線程)
- 如果t1沒有釋放鎖,t2自旋一次之后還是沒有獲取到鎖則park 排隊(duì)
- t3來加鎖,t1沒有釋放鎖,t2這個時候已經(jīng)排隊(duì)(和t2的區(qū)別在于t3入隊(duì)之后不會自旋,直接排 隊(duì))。
- t3 因?yàn)閠1沒有釋放鎖,所以t3肯定拿不到鎖,肯定會調(diào)用addWaiter–enq方法入隊(duì)。
四、解鎖unlock
第一種情況:
- 只有一個t1上鎖了,當(dāng)調(diào)用unlock解鎖的時候,sync.release(1);
- 把鎖的狀態(tài)改為自由狀態(tài)
- boolean free = false; 只是標(biāo)識一下目前還沒有釋放鎖成功,因?yàn)槟銉H僅把鎖改成了自由狀態(tài),線程沒有釋放(而且還有可能是重入),所以這個變量是一個過渡變量。
- setExclusiveOwnerThread(null) 把持有鎖的線程改為null 鎖徹底釋放了
- 以上是釋放鎖,接下來可能需要喚醒隊(duì)列當(dāng)中的阻塞線程去獲取鎖(因?yàn)橛锌赡懿恍枰獑拘眩?/li>
- Node h = head;拿到隊(duì)頭if (h != null && h.waitStatus != 0)判斷是否有對頭,是否有線程在排隊(duì)
- 由于當(dāng)前這種情況肯定沒有人排隊(duì)則不需要喚醒,則return true 標(biāo)識解鎖成功
第二種情況:
就是隊(duì)列當(dāng)中有線程排隊(duì),比如t2
- 調(diào)用tryRelease方法釋放
- if (h != null && h.waitStatus != 0) 判斷是否需要喚醒下一個,當(dāng)前這種情況肯定需要喚醒一個
- Node h = head;把頭結(jié)點(diǎn)傳給了unparkSuccessor,unparkSuccessor(h);
- 得到隊(duì)列當(dāng)中第一個排隊(duì)的線程, 也就是t2所標(biāo)識的node對象LockSupport.unpark(s.thread); 喚醒t2線程
- 由于t2在lock方法中被阻塞那么喚醒則也是從lock方法中被喚醒往下執(zhí)行
private final boolean parkAndCheckInterrupt() {
//簡單的喚醒t2
LockSupport.park(this);
//這里為什么需要調(diào)用一下Thread.interrupted()
return Thread.interrupted();
}
Thread.interrupted()這個方法主要干嘛?清除打斷標(biāo)記(復(fù)位)
五、內(nèi)部類

Sync類存在如下方法和作用如下。

NonfairSync類 NonfairSync類繼承了Sync類,表示采用非公平策略獲取鎖,其實(shí)現(xiàn)了Sync類中抽象的lock方法,源碼如下:
// 非公平鎖
static final class NonfairSync extends Sync {
// 版本號
private static final long serialVersionUID = 7316153563782823691L;
// 獲得鎖
final void lock() {
if (compareAndSetState(0, 1)) // 比較并設(shè)置狀態(tài)成功,狀態(tài)0表示鎖沒有被占用
// 把當(dāng)前線程設(shè)置獨(dú)占了鎖
setExclusiveOwnerThread(Thread.currentThread());
else // 鎖已經(jīng)被占用,或者set失敗
// 以獨(dú)占模式獲取對象,忽略中斷
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
從lock方法的源碼可知,每一次都嘗試獲取鎖,而并不會按照公平等待的原則進(jìn)行等待,讓等待時間最久的線程獲得鎖。
FairSyn類 FairSync類也繼承了Sync類,表示采用公平策略獲取鎖,其實(shí)現(xiàn)了Sync類中的抽象lock方法,源碼如下:
// 公平鎖
static final class FairSync extends Sync {
// 版本序列化
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
// 以獨(dú)占模式獲取對象,忽略中斷
acquire(1);
}
/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
// 嘗試公平獲取鎖
protected final boolean tryAcquire(int acquires) {
// 獲取當(dāng)前線程
final Thread current = Thread.currentThread();
// 獲取狀態(tài)
int c = getState();
if (c == 0) { // 狀態(tài)為0
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) { // 不存在已經(jīng)等待更久的線程并且比較并且設(shè)置狀態(tài)成功
// 設(shè)置當(dāng)前線程獨(dú)占
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) { // 狀態(tài)不為0,即資源已經(jīng)被線程占據(jù)
// 下一個狀態(tài)
int nextc = c + acquires;
if (nextc < 0) // 超過了int的表示范圍
throw new Error("Maximum lock count exceeded");
// 設(shè)置狀態(tài)
setState(nextc);
return true;
}
return false;
}
}
跟蹤lock方法的源碼可知,當(dāng)資源空閑時,它總是會先判斷sync隊(duì)列(AbstractQueuedSynchronizer中的數(shù)據(jù)結(jié)構(gòu))是否有等待時間更長的線程,如果存在,則將該線程加入到等待隊(duì)列的尾部,實(shí)現(xiàn)了公平獲取原則。其中,F(xiàn)airSync類的lock的方法調(diào)用如下,只給出了主要的方法。

可以看出只要資源被其他線程占用,該線程就會添加到sync queue中的尾部,而不會先嘗試獲取資源。這也是和Nonfair最大的區(qū)別,Nonfair每一次都會嘗試去獲取資源,如果此時該資源恰好被釋放,則會被當(dāng)前線程獲取,這就造成了不公平的現(xiàn)象,當(dāng)獲取不成功,再加入隊(duì)列尾部。
六、類的構(gòu)造函數(shù)
ReentrantLock()型構(gòu)造函數(shù);默認(rèn)是采用的非公平策略獲取鎖
public ReentrantLock() {
// 默認(rèn)非公平策略
sync = new NonfairSync();
}
ReentrantLock(boolean)型構(gòu)造函數(shù)
可以傳遞參數(shù)確定采用公平策略或者是非公平策略,參數(shù)為true表示公平策略,否則,采用非公平策略:
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
核心函數(shù)分析
通過分析ReentrantLock的源碼,可知對其操作都轉(zhuǎn)化為對Sync對象的操作,由于Sync繼承了AQS,所以基本上都可以轉(zhuǎn)化為對AQS的操作。如將ReentrantLock的lock函數(shù)轉(zhuǎn)化為對Sync的lock函數(shù)的調(diào)用,而具體會根據(jù)采用的策略(如公平策略或者非公平策略)的不同而調(diào)用到Sync的不同子類。 所以可知,在ReentrantLock的背后,是AQS對其服務(wù)提供了支持。
到此這篇關(guān)于Java中的ReentrantLock原理解析的文章就介紹到這了,更多相關(guān)ReentrantLock原理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Java多線程并發(fā)JUC包ReentrantLock顯示鎖的用法
- Java使用ReentrantLock進(jìn)行加解鎖的示例代碼
- Java中的Lock與ReentrantLock深入解析
- Java中的ReentrantLock實(shí)現(xiàn)原理及代碼演示
- Java可重入鎖reentrantLock解析
- Java并發(fā)編程之ReentrantLock解析
- Java并發(fā)編程中的ReentrantLock類詳解
- Java并發(fā)編程中的ReentrantLock詳解
- 淺談一下Java中的ReentrantLock
- Java ReentrantLock的使用與應(yīng)用實(shí)戰(zhàn)
相關(guān)文章
springboot+vue實(shí)現(xiàn)登錄功能
這篇文章主要為大家詳細(xì)介紹了springboot+vue實(shí)現(xiàn)登錄功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-05-05
Spring Cloud根據(jù)服務(wù)名獲取服務(wù)的ip端口問題
這篇文章主要介紹了Spring Cloud根據(jù)服務(wù)名獲取服務(wù)的ip端口,本篇示例我就以Nacos注冊中心為例了,下面是我注冊的兩個服務(wù),需要的朋友可以參考下2022-09-09
Java使用IO流實(shí)現(xiàn)音頻的剪切和拼接
這篇文章主要為大家詳細(xì)介紹了Java使用IO流實(shí)現(xiàn)音頻的剪切和拼接,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-06-06
Springboot?hibernate-validator?6.x快速校驗(yàn)示例代碼
這篇文章主要介紹了Springboot?hibernate-validator?6.x校驗(yàn),本文以6.2.1.Final版本為例解決了log4j版本的漏洞問題,通過實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2021-12-12
實(shí)例講解Java設(shè)計(jì)模式編程中如何運(yùn)用代理模式
這篇文章主要介紹了Java設(shè)計(jì)模式編程中如何運(yùn)用代理模式,文中舉了普通代理和強(qiáng)制代理的例子作為代理模式的擴(kuò)展內(nèi)容,需要的朋友可以參考下2016-02-02
Java Hutool 包工具類推薦 ExcelUtil詳解
這篇文章主要介紹了Java Hutool 包工具類推薦 ExcelUtil詳解,需要引入hutool包,版本號可根據(jù)實(shí)際情況更換,除hutool包之外,還需要引入操作Excel必要包,本文給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-09-09
Java之mybatis使用limit實(shí)現(xiàn)分頁案例講解
這篇文章主要介紹了Java之mybatis使用limit實(shí)現(xiàn)分頁案例講解,本篇文章通過簡要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-08-08

