目錄
- 前言
- Reentrantlock結構構成
- 源碼剖析
-
- AQS部分解析
- Sync源碼
- NonfairSync
- FairSync 解析
- Reentrantlock
- 結尾
前言
Reentrantlock是可重入的互斥鎖,具有與Synchronized相同的功能,但是卻比Synchronized更加靈活。
Reentrantlock底層基于AbstractQueuedSynchronizer實作,AbstractQueuedSynchronizer抽象類定義了一套多線程通路共享資源的同步模闆,解決了實作同步器時涉及的大量細節問題,隻有少量細節需要自己設定。(AbstractQueuedSynchronizer為加鎖和解鎖過程提供了同步的模闆方法)。
Reentrantlock結構構成
Reentrantlock實作了Lock接口,Reentrantlock具有内部類Sync、NonfairSync、FairSync,其中Sync繼承于AbstractQueuedSynchronizer,Sync實作了釋放資源的細節,NonfairSync是Sync的子類,實作鎖的非公平模式,FairSync也是Sync的子類,實作了鎖的公平模式。
源碼剖析
了解了Reentrantlock的整體結構,接下來我們一個塊一個塊地去分析。
首先我們學習一下Lock接口的源碼
public interface Lock {
// 擷取 - 不可中斷
void lock();
// 擷取 - 可以中斷
void lockInterruptibly() throws InterruptedException;
// 擷取鎖 - 可以傳回擷取鎖是否成功
boolean tryLock();
// 擷取鎖(可以設定時間) - 可以中斷
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
// 釋放鎖
void unlock();
// 條件變量
Condition newCondition();
}
/*
這裡的中斷指的是如果目前線程擷取鎖但是未擷取到鎖,我們可以使用中斷方法中斷擷取鎖,而不會使它陷入阻塞狀态。
*/
AQS部分解析
AQS中有一個CLH隊列,當擷取鎖失敗時會将線程加入CLH隊列,當一個鎖是非公平鎖時,目前線程擷取鎖時會和隊列的首部線程争搶,成功則設定目前線程為擷取鎖線程,失敗則加入隊列;當一個鎖是公平鎖時,如果隊列為空,則目前線程擷取鎖,如果隊列不為空,則一定是隊列首部線程擷取到鎖,将目前線程加入CLH隊列
Sync源碼
在Reentrantlock中有一個Sync對象,當我們使用的鎖為非公平鎖時,可以利用多态的特性,使其指向NonfairSync,同理,當我們使用的鎖為公平鎖的時候,我們可以将其指向FairSync。又因為Sync繼承了AbstractQueuedSynchronizer,AbstractQueuedSynchronizer提供了加鎖和解鎖過程的模闆方法,是以我們可以看出Sync在Reentrantlock中的重要性。
接下來我們就來看看Sync的源碼
//Sync的部分源碼
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
// 擷取鎖,由子類實作
abstract void lock();
// 非公平擷取資源
final boolean nonfairTryAcquire(int acquires) {
// 擷取目前線程
final Thread current = Thread.currentThread();
// 擷取目前狀态
int c = getState();
// 如果state == 0, 說明現在沒有線程擷取到鎖,則目前線程嘗試加鎖
if (c == 0) {
// 嘗試加鎖
if (compareAndSetState(0, acquires)) {
// 加鎖成功,設定目前線程為擷取鎖線程
setExclusiveOwnerThread(current);
return true;
}
}
// 如果state != 0, 但是目前線程已經是擷取到鎖的線程,說明目前可以多次擷取鎖(可重入)
else if (current == getExclusiveOwnerThread()) {
// 擷取資源,state + 1
int nextc = c + acquires;
// 如果state < 0, 抛出異常
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
// 設定state的狀态,此處不需要CAS,因為目前持有鎖的線程隻有一個,就是自己。
setState(nextc);
// 傳回true
return true;
}
// 傳回false
return false;
}
// 釋放資源
protected final boolean tryRelease(int releases) {
// state 減去 releases
int c = getState() - releases;
// 如果目前線程不是擷取鎖的線程,那肯定無法釋放資源,就抛出異常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
// 預設釋放資源為false
boolean free = false;
// 如果state為0,說明已經釋放了全部資源,設定目前持有鎖的線程為null,傳回true
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
// 否則說明沒有釋放全部資源(重入鎖),傳回false
setState(c);
return free;
}
// 建立條件變量
final ConditionObject newCondition() {
return new ConditionObject();
}
}
我們可以發現,Sync實作了非公平擷取鎖的方法,這是給子類NonfairSync用的,但是卻沒有實作公平擷取鎖的方法,需要子類FairSync自己實作。另外Sync還實作了釋放資源的方法。
釋放資源的流程:
- 判斷目前線程是否是持有鎖的線程,是就往下執行,不是則抛出異常
- 得到state減1的值,之後判斷state是否等于0,等于0設定持有鎖線程為null,傳回true表示釋放資源成功,不等于0就設定state為state-1,傳回false,代表釋放資源失敗(重入鎖)。
這是釋放資源的流程圖
說完了Sync,那麼我們接下來說一下NonfairSync
NonfairSync
NonfairSync是Sync的子類,用于非公平擷取資源
// NonfairSync的源碼
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
// 加鎖
final void lock() {
// 執行CAS操作
if (compareAndSetState(0, 1))
// CAS操作成功,設定目前線程為持有鎖線程
setExclusiveOwnerThread(Thread.currentThread());
else
// 失敗執行AQS擷取鎖模闆流程
acquire(1);
}
// 擷取資源,調用父類Sync實作的非公平擷取資源方法
protected final boolean tryAcquire(int acquires) {
// 直接使用父類的非公平擷取資源
return nonfairTryAcquire(acquires);
}
}
// AQS中的acquire(int arg)方法
public final void acquire(int arg) {
// 再執行一次擷取資源方法,成功就結束,失敗就将目前線程加入CLH隊列
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
/*
我們看一下父類Sync實作非公平擷取資源的細節
// 非公平擷取資源
final boolean nonfairTryAcquire(int acquires) {
// 擷取目前線程
final Thread current = Thread.currentThread();
// 擷取目前狀态
int c = getState();
// 如果state == 0, 說明現在沒有線程擷取到鎖,則目前線程嘗試加鎖
if (c == 0) {
// 嘗試加鎖
if (compareAndSetState(0, acquires)) {
// 加鎖成功,設定目前線程為擷取鎖線程
setExclusiveOwnerThread(current);
return true;
}
}
// 如果state != 0, 但是目前線程已經是擷取到鎖的線程,說明目前可以多次擷取鎖(可重入)
else if (current == getExclusiveOwnerThread()) {
// 擷取資源,state + 1
int nextc = c + acquires;
// 如果state < 0, 抛出異常
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
// 設定state的狀态,此處不需要CAS,因為目前持有鎖的線程隻有一個,就是自己。
setState(nextc);
// 傳回true
return true;
}
// 傳回false
return false;
}
*/
我們說一下非公平擷取資源的方式:
- 判斷state是否等于0,等于0就嘗試加鎖,加鎖成功就設定目前線程為持有鎖的線程,傳回true,否則往下執行
- 判斷目前線程和擷取鎖的線程是不是同一個線程,是同一個線程,就讓state+1,傳回true,否則傳回false
FairSync 解析
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
// 執行AQS擷取鎖模闆函數
final void lock() {
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// state為0,之後判斷目前線程是不是隊列被喚醒的線程
if (c == 0) {
// 是隊列被喚醒的線程,嘗試加鎖
// hasQueuedPredecessors()傳回false說明目前線程是隊列的頭部線程,就嘗試擷取鎖
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
// 加鎖成功就設定目前線程為持有鎖的線程,傳回true
setExclusiveOwnerThread(current);
return true;
}
}
// 目前線程和持有鎖的線程是同一個線程,就讓state+1,設定state,傳回true,否則傳回false
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
// 設定state
setState(nextc);
return true;
}
return false;
}
}
FairSync是公平擷取鎖,擷取資源流程和NonfairSync擷取資源的流程很相似,隻是加了一個判斷,判斷如果目前線程不在隊列就加入隊列,如果目前線程在隊列就判斷目前線程的前面還有沒有線程。
Reentrantlock
最後我們說一下Reentrantlock的源碼
// Reentrantlock的部分源碼
public class ReentrantLock implements Lock, java.io.Serializable {
private static final long serialVersionUID = 7373984872572414699L;
// Sync對象
private final Sync sync;
// 預設為非公平鎖
public ReentrantLock() {
sync = new NonfairSync();
}
// 設定鎖為公平鎖還是非公平鎖
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
// 擷取鎖
public void lock() {
sync.lock();
}
// 擷取鎖 - 可以中斷
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
// 擷取鎖 - 傳回擷取鎖成功還是失敗
public boolean tryLock() {
return sync.nonfairTryAcquire(1);
}
// 擷取鎖 - 可以設定時間,可以中斷
public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
// 釋放鎖
public void unlock() {
// 使用Sync的release方法
sync.release(1);
}
// 條件變量
public Condition newCondition() {
// 使用Sync的建立條件變量方法
return sync.newCondition();
}
}
Reentrantlock有兩個構造方法,無參的構造方法為非公平鎖,有參的構造方法可以設定公平鎖,Reentrantlock鎖的實作重點靠裡面的Sync對象,Sync繼承了AQS,又有兩個子類,使用Sync對象可以很好的實作功能。
結尾
最後我們說一下與Synchronized相比Reentrantlock有以下幾個特性
- 可重入
- 可中斷
- 可以設定逾時時間
- 可以設定為公平鎖
- 鎖的粒度更小,功能更靈活
相信學習了Reentrantlock的結構和内部源碼,我們對于它的使用肯定可以更加得心應手。
學習于:
程式猿阿星
米蘭的小鐵匠z