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

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

