Java可重入鎖ReentrantLock詳解
Lock接口
在講 ReentrantLock 前,我們先熟悉一下 Lock 接口。在 Lock 接口出現(xiàn)之前, Java 程序主要是靠 synchronized 關(guān)鍵字實(shí)現(xiàn)鎖功能的,而JDK1.5之后,并發(fā)包中增加了 Lock 接口,它提供了與 synchronized 一樣的鎖功能。雖然它失去了像synchronize關(guān)鍵字隱式加鎖解鎖的便捷性,但是卻擁有了鎖獲取和釋放的可操作性,可中斷的獲取鎖以及超時(shí)獲取鎖等多種 synchronized 關(guān)鍵字所不具備的同步特性。 Lock 接口的定義如下
public interface Lock {
//獲取鎖,若當(dāng)前鎖被其他線程獲取,則此線程會(huì)阻塞等待lock被釋放
//使用lock方法需要顯式地去釋放鎖,即使發(fā)生異常時(shí)也不會(huì)自動(dòng)釋放鎖。
void lock();
//作用同lock方法,并且在獲取鎖的過(guò)程中可以響應(yīng)中斷
void lockInterruptibly() throws InterruptedException;
//嘗試獲取鎖,獲取成功返回true,失敗則返回false
//即該方法不會(huì)導(dǎo)致線程阻塞,無(wú)論如何都會(huì)返回
boolean tryLock();
//作用同tryLock方法,如果獲取到鎖直接返回true
//新增的特性是如果獲取不到鎖,會(huì)等待一段時(shí)間,且在等待的過(guò)程可以響應(yīng)中斷,一旦超過(guò)等待時(shí)間仍獲取不到鎖,就返回false
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
//釋放鎖
void unlock();
//返回一個(gè)綁定該lock的Condition對(duì)象,這個(gè)后續(xù)在Condition專(zhuān)題會(huì)詳細(xì)講解,本文先不贅述
Condition newCondition();
}ReentrantLock 完全實(shí)現(xiàn)了Lock接口,也是JDK中唯一實(shí)現(xiàn)Lock接口的類(lèi)(其余都是一些內(nèi)部類(lèi))。Lock接口的基本模板使用如下所示
//創(chuàng)建lock實(shí)例
Lock lock = new ReentrantLock();
try {
//加鎖
lock.lock();
// do something...
} finally {
//解鎖
lock.unlock();
}什么是ReentrantLock
ReentrantLock是一個(gè)可重入且獨(dú)占式的鎖,是一種遞歸無(wú)阻塞的同步機(jī)制。
它支持重復(fù)進(jìn)入鎖,即該鎖能夠支持一個(gè)線程對(duì)資源的重復(fù)加鎖。
除此之外,該鎖的還支持獲取鎖時(shí)的公平和非公平性選擇。
可重入概念
重進(jìn)入是指任意線程在獲取到鎖之后能夠再次獲取該鎖而不會(huì)被鎖阻塞,該特性的首先需要解決以下兩個(gè)問(wèn)題:
- 線程再次獲取鎖:所需要去識(shí)別獲取鎖的線程是否為當(dāng)前占據(jù)鎖的線程,如果是,則再次獲取成功;
- 鎖的最終釋放:線程重復(fù)n次獲取了鎖,隨后在第n次釋放該鎖后,其它線程能夠獲取到該鎖。鎖的最終釋放要求鎖對(duì)于獲取進(jìn)行計(jì)數(shù)自增,計(jì)數(shù)表示當(dāng)前線程被重復(fù)獲取的次數(shù),而被釋放時(shí),計(jì)數(shù)自減,當(dāng)計(jì)數(shù)為0時(shí)表示鎖已經(jīng)成功釋放。
ReentrantLock和Synchronized對(duì)比
共同點(diǎn)
- 都可以協(xié)調(diào)多線程對(duì)共享對(duì)象、變量的訪問(wèn)
- 都是可重入的,同一個(gè)線程可以多次獲得同一個(gè)鎖
- 都是阻塞式地同步,即一個(gè)線程獲取了鎖,其他訪問(wèn)該鎖的線程必須阻塞在同步塊外等待
- 性能方面:Synchronized在JDK1.6的一波優(yōu)化后性能與lock差別不大
區(qū)別
- ReentrantLock需要自己手動(dòng)地獲取和釋放鎖,而synchronized關(guān)鍵字可以隱式獲得和釋放,無(wú)需用戶(hù)操心。
- ReentrantLock具有響應(yīng)中斷,超時(shí)獲取,公平非公平鎖和利用Condition綁定多個(gè)條件等特性,而synchronized不具備這些特性
- ReentrantLock是API級(jí)別的,而synchronized是JVM級(jí)別的
- ReentrantLock底層實(shí)現(xiàn)采用的是同步非阻塞,樂(lè)觀并發(fā)(CAS)策略。而synchronized是同步阻塞,悲觀并發(fā)。
類(lèi)的繼承關(guān)系
類(lèi)總覽:

ReentrantLock實(shí)現(xiàn)類(lèi)Lock和Serializable接口。
public class ReentrantLock implements Lock, java.io.Serializable
類(lèi)的內(nèi)部類(lèi)
ReentrantLock 類(lèi)內(nèi)部總共存在Sync、NonfairSync、FairSync三個(gè)類(lèi),其中 NonfairSync 與 FairSync 類(lèi)繼承自 Sync 類(lèi), Sync 類(lèi)繼承自 AbstractQueuedSynchronizer 抽象類(lèi)。
Sync
Sync類(lèi)繼承自AQS,它有兩個(gè)子類(lèi),分別實(shí)現(xiàn)公平鎖和非公平鎖
abstract static class Sync extends AbstractQueuedSynchronizer {
//序列化版本號(hào)
private static final long serialVersionUID = -5179523762034025860L;
//獲取鎖,需要子類(lèi)自己實(shí)現(xiàn)
abstract void lock();
//非公平方式獲取鎖
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
//釋放鎖
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
//判斷資源釋放被當(dāng)前線程占用
protected final boolean isHeldExclusively() {
return getExclusiveOwnerThread() == Thread.currentThread();
}
//創(chuàng)建一個(gè)新條件
final ConditionObject newCondition() {
return new ConditionObject();
}
// 返回占有資源的線程
final Thread getOwner() {
return getState() == 0 ? null : getExclusiveOwnerThread();
}
//返回狀態(tài)
final int getHoldCount() {
return isHeldExclusively() ? getState() : 0;
}
//判斷資源是否被占用
final boolean isLocked() {
return getState() != 0;
}
//自定義反序列化
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
setState(0); // reset to unlocked state
}
}NonfairSync
NonfairSync類(lèi)繼承了Sync類(lèi),表示采用非公平策略獲取鎖,其實(shí)現(xiàn)了Sync類(lèi)中抽象的lock方法
static final class NonfairSync extends Sync {
//序列化版本號(hào)
private static final long serialVersionUID = 7316153563782823691L;
//獲取鎖
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
//嘗試獲取鎖
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}方法細(xì)節(jié)后面詳細(xì)說(shuō)明
FairSync
FairSync類(lèi)也繼承了Sync類(lèi),表示采用公平策略獲取鎖,其也實(shí)現(xiàn)了Sync類(lèi)中的抽象lock方法
static final class FairSync extends Sync {
//序列化版本號(hào)
private static final long serialVersionUID = -3000897897090466540L;
//獲取鎖
final void lock() {
acquire(1);
}
//嘗試公平獲取鎖
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}方法細(xì)節(jié)后面詳細(xì)說(shuō)明
類(lèi)的屬性
//序列化版本號(hào) private static final long serialVersionUID = 7373984872572414699L; //同步器,繼承自AQS,提供各種核心操作 private final Sync sync;
sync屬性非常重要,大部分的鎖操作直接轉(zhuǎn)化為該屬性的方法調(diào)用。
鎖類(lèi)型
ReentrantLock 分為公平鎖和非公平鎖,分別有它的兩個(gè)內(nèi)部類(lèi) FairSync 和 NonfairSync 實(shí)現(xiàn)。
公平鎖
加鎖前檢查是否有排隊(duì)等待的線程,優(yōu)先排隊(duì)等待的線程,先來(lái)先得,即遵循FIFO原則。
非公平鎖
加鎖時(shí)不考慮排隊(duì)等待問(wèn)題,直接嘗試獲取鎖,獲取不到自動(dòng)到隊(duì)尾等待
是否公平可以通過(guò)構(gòu)造方法指定。
//無(wú)參構(gòu)造函數(shù),默認(rèn)為非公平鎖
public ReentrantLock() {
sync = new NonfairSync();
}
//有參構(gòu)造函數(shù),傳遞boolean類(lèi)型參數(shù)fair
//fair=false:非公平鎖
//fair=true:公平鎖
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}注:公平鎖為了保證時(shí)間上的絕對(duì)順序,需要頻繁的上下文切換,而非公平鎖會(huì)降低一定的上下文切換,降低性能開(kāi)銷(xiāo)。因此, ReentrantLock 默認(rèn)選擇的是非公平鎖,則是為了減少一部分上下文切換,保證了系統(tǒng)更大的吞吐量。
獲取鎖
在講解 ReentrantLock 的源碼前,需要大家對(duì)AQS的源碼熟悉
公平鎖的獲取
我們知道,lock的獲取鎖一般使用如下方法
lock.lock();
那我們直接沿著該方法往下看,先看lock方法的實(shí)現(xiàn)
public void lock() {
sync.lock();
}調(diào)用屬性sync的lock方法,繼續(xù)深入(此時(shí)是公平鎖,所以調(diào)用的是 FairSync 對(duì)象中的 lock 方法)
final void lock() {
acquire(1);
}繼續(xù)查看acquire方法
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}看到這里想必大家已經(jīng)很熟悉了,這就是AQS的acquire方法,這里就不再贅述,大家可以參考之前的AQS源碼分析文章。但是我們需要分析一下tryAcquire方法,因?yàn)樵摲椒ㄓ蠷eentrantLock中FairSync類(lèi)自己實(shí)現(xiàn)的,源碼如下
protected final boolean tryAcquire(int acquires) {
//獲取當(dāng)前線程
final Thread current = Thread.currentThread();
//獲取同步狀態(tài)
int c = getState();
//如果同步狀態(tài)為0表示當(dāng)前資源空閑
if (c == 0) {
//在CAS獲取鎖前需要調(diào)用方法hasQueuedPredecessors來(lái)判斷隊(duì)列中是否存在其他正在排隊(duì)的節(jié)點(diǎn),如果存在返回true,不僅如此if塊,否則返回false,進(jìn)行CAS操作
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
//設(shè)置當(dāng)前資源的持有線程為當(dāng)前線程
setExclusiveOwnerThread(current);
return true;
}
}
//如果c不等于0,且當(dāng)前持有鎖線程不等于當(dāng)前線程,直接返回false,加鎖失敗
//如果c不等于0,且當(dāng)前持有鎖的線程等于當(dāng)前線程,表示這是一次重入,就會(huì)把狀態(tài)+1,結(jié)束后返回true,即重入鎖返回true
//這塊代碼側(cè)面說(shuō)明Reentrantlock是可以重入的
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}這里需要詳細(xì)說(shuō)明一下 hasQueuedPredecessors 方法(該方法的代碼也是整個(gè) Reentrantlock 代碼中的精華部分之一),需要注意一下,該方法如果返回false表示當(dāng)前線程可以直接獲取鎖,否則獲取鎖失敗。代碼如下
public final boolean hasQueuedPredecessors() {
//保存尾結(jié)點(diǎn)
Node t = tail;
//保存頭結(jié)點(diǎn)
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}重點(diǎn)看一下return后面的判斷
h != t &&((s = h.next) == null || s.thread != Thread.currentThread())
- 先看條件h != t
- 如果h=t,大概分為以下兩種情況
- 隊(duì)列未初始化,此時(shí)h=t=null,說(shuō)明肯定沒(méi)有排隊(duì)等待的線程,可以直接獲取鎖,所以返回false
- 隊(duì)列已經(jīng)初始化 ,但是不存在排隊(duì)等待的線程。我們知道隊(duì)列在初始化的時(shí)候會(huì)新建個(gè)節(jié)點(diǎn)(thread=null)作為頭結(jié)點(diǎn),往后需要排隊(duì)的線程鏈接到這之后。因此當(dāng)隊(duì)列只有一個(gè)節(jié)點(diǎn)時(shí),滿(mǎn)足h=t,此時(shí)返回false,可以直接獲取鎖
- 如果h!=t,說(shuō)明隊(duì)列節(jié)點(diǎn)個(gè)數(shù)大于1,即肯定存在排隊(duì)等待的線程,此時(shí)h!=t返回true,需要繼續(xù)進(jìn)行下一步判斷
- 如果h=t,大概分為以下兩種情況
- 再看條件(s = h.next) == null || s.thread != Thread.currentThread()
- 先看(s = h.next) == null:通過(guò)上面的分析我們知道隊(duì)列的節(jié)點(diǎn)個(gè)數(shù)大于1,所以s(即頭結(jié)點(diǎn)的下一節(jié)點(diǎn))肯定不為空,返回false;
- 再看s.thread != Thread.currentThread():能夠執(zhí)行到此處,整個(gè)return后面的判斷語(yǔ)句的結(jié)果直接取決于當(dāng)前判斷。這里判斷s節(jié)點(diǎn)對(duì)應(yīng)的線程是否是當(dāng)前線程,如果是說(shuō)明此時(shí)排隊(duì)的線程就是當(dāng)前線程,即可以獲取鎖,返回false。如果不是則表示當(dāng)前還沒(méi)有輪到我(當(dāng)前線程)獲取鎖,返回true。
總結(jié)一下 hasQueuedPredecessors 方法:該方法的作用就是查看是否有排隊(duì)的線程,如果沒(méi)有直接返回false,代表當(dāng)前線程可以獲取鎖,否則再查看第一個(gè)排隊(duì)的線程是否是自己,如果是則返回false,可以獲取鎖,否則就獲取鎖失敗,進(jìn)入后續(xù)的入隊(duì)操作。
非公平鎖的獲取
非公平鎖的獲取,同樣我們來(lái)到 NonfairSync 對(duì)象中的 lock 方法
final void lock() {
//直接嘗試獲取鎖
if (compareAndSetState(0, 1))
//獲取鎖成功后
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}非公平鎖的lock方法一開(kāi)始就嘗試獲取CAS獲取鎖,如果CAS失敗再調(diào)用方法acquire去進(jìn)行相應(yīng)的排隊(duì)操作。
釋放鎖
公平鎖和非公平鎖的釋放流程都是一樣的
public void unlock() {
sync.release(1);
}調(diào)用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;
}這里我們?cè)敿?xì)講解一下tryRelease方法,代碼如下
protected final boolean tryRelease(int releases) {
//計(jì)算釋放后state值
int c = getState() - releases;
//如果獲取鎖的線程不是當(dāng)前線程,直接拋異常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
//鎖被重入的次數(shù)為0,表示鎖被釋放,清空獨(dú)占線程
if (c == 0) {
free = true;
//清空獨(dú)占線程
setExclusiveOwnerThread(null);
}
//設(shè)置state值
setState(c);
//返回鎖是否被釋放
return free;
}方法詳解見(jiàn)注釋?zhuān)@里需要注意一下,調(diào)用unlock方法前需要保證鎖的占有者必須是當(dāng)前方法的調(diào)用者,否則或拋出IllegalMonitorStateException異常。
到此這篇關(guān)于Java可重入鎖ReentrantLock詳解的文章就介紹到這了,更多相關(guān)ReentrantLock詳解內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java通過(guò)SSH連接路由器輸入命令并讀取響應(yīng)的操作方法
最近需要讀取和修改華為路由器的配置,使用Java語(yǔ)言開(kāi)發(fā),通過(guò)SSH連接,輸入命令并讀取響應(yīng),接下來(lái)通過(guò)本文給大家介紹下Java通過(guò)SSH連接路由器,輸入命令并讀取響應(yīng),需要的朋友可以參考下2024-01-01
java中判斷字段真實(shí)長(zhǎng)度的實(shí)例(中文2個(gè)字符,英文1個(gè)字符)
下面小編就為大家?guī)?lái)一篇java中判斷字段真實(shí)長(zhǎng)度的實(shí)例(中文2個(gè)字符,英文1個(gè)字符)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-01-01
使用IDEA創(chuàng)建Java Web項(xiàng)目并部署訪問(wèn)的圖文教程
本文通過(guò)圖文并茂的形式給大家介紹了使用IDEA創(chuàng)建Java Web項(xiàng)目并部署訪問(wèn)的教程,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2018-08-08
如何使用Java將word解析出來(lái)(包含格式和圖片)
今天遇到一個(gè)讀取word模板內(nèi)容的需求,下面這篇文章主要給大家介紹了關(guān)于如何使用Java將word解析出來(lái),包含格式和圖片,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-12-12
springboot集成junit編寫(xiě)單元測(cè)試實(shí)戰(zhàn)
在做單元測(cè)試時(shí),代碼覆蓋率常常被拿來(lái)作為衡量測(cè)試好壞的指標(biāo),本文主要介紹了springboot集成junit編寫(xiě)單元測(cè)試實(shí)戰(zhàn),文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-02-02
java 中Executor, ExecutorService 和 Executors 間的不同
這篇文章主要介紹了java 中Executor, ExecutorService 和 Executors 間的不同的相關(guān)資料,需要的朋友可以參考下2017-06-06
深入理解Spring注解@Async解決異步調(diào)用問(wèn)題
這篇文章主要介紹了深入理解Spring注解@Async解決異步調(diào)用問(wèn)題,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07

