開始這篇部落格之前,博主預設大家都是看過AQS原始碼的~什麼居然沒看過?猛戳下方???
全網最詳細的AbstractQueuedSynchronizer(AQS)原始碼剖析(一)AQS基礎
全網最詳細的AbstractQueuedSynchronizer(AQS)原始碼剖析(二)資源的獲取和釋放
全網最詳細的AbstractQueuedSynchronizer(AQS)原始碼剖析(三)條件變數
介紹
ReentrantLock
是可重入鎖,是JUC提供的一種最常用的鎖。“可重入”的意思就是:同一個執行緒可以無條件地反覆獲得已經持有的鎖
ReentrantLock
有公平鎖和非公平鎖兩種模式,底層使用的正是AbstractQueuedSynchronizer
這個偉大的併發工具
ReentrantLock
的結構如下圖所示:
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();
}
使用ReentrantLock
,主要使用lock
和unlock
方法,也會用到newCondition
來建立條件變數,實現一些條件同步功能
回到上面的結構圖,可以看到,ReentrantLock
的功能主要是藉助其內部類Sync
來實現,而Sync類是繼承了AbstractQueuedSynchronizer
,並衍生出兩個子類FairSync
、NonfairSync
,分別對應公平鎖和非公平鎖兩種模式。實際應用中,一般非公平鎖的效率要高於公平鎖
版權:本文版權歸作者和部落格園共有
轉載:歡迎轉載,但未經作者同意,必須保留此段宣告;必須在文章中給出原文連線;否則必究法律責任
Sync
ReentrantLock
中的sync
域是是一個Sync
類物件,ReentrantLock
使用Sync
類實現主要的功能,Sync
是ReentrantLock
的內部類:
/** Synchronizer providing all implementation mechanics */
private final Sync sync;
Sync
類繼承了AQS,使用AQS的state
作為鎖的重入數,其原始碼如下:
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
// 用於輔助ReentrantLock執行Lock介面的lock方法,所以定義了一個同名lock方法
abstract void lock();
// 執行非公平的tryLock,是非公平鎖子類實現的tryAcquire方法的主要邏輯,也是ReentrantLock的tryLock的主要邏輯?
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;
}
// 為了支援使用條件變數,Sync實現了AQS中的isHeldExclusively,並提供了newCondition方法建立條件變數
protected final boolean isHeldExclusively() {
return getExclusiveOwnerThread() == Thread.currentThread();
}
final ConditionObject newCondition() {
return new ConditionObject();
}
// ReentrantLock的三個同名方法,都委託給了下面這三個方法,用於獲取鎖的一些資訊
final Thread getOwner() {
return getState() == 0 ? null : getExclusiveOwnerThread();
}
final int getHoldCount() {
return isHeldExclusively() ? getState() : 0;
}
final boolean isLocked() {
return getState() != 0;
}
/**
* Reconstitutes the instance from a stream (that is, deserializes it).
*/
private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
setState(0); // reset to unlocked state
}
}
總結一下,Sync
類有以下幾個作用:
- 定義抽象
lock
方法:Sync
定義了lock
這一個抽象方法,強迫兩個子類去實現,而ReentrantLock
的lock
方法就可以直接委託給Sync
的lock
方法去執行 - 非公平模式的嘗試獲取鎖:
Sync
提供了nonfairTryAcquire
方法,提供非公平嘗試獲取鎖的方法。不僅非公平子類實現tryAcquire
方法需要委託給nonfairTryAcquire
來處理,而且ReentrantLock
中的tryLock
方法也會委託給它來處理 - 嘗試釋放鎖:
Sync
實現了AQS中的tryRelease
方法,因為不管是公平模式還是非公平模式,釋放鎖的邏輯都是相同的,因此在Sync
這一層就提供了具體的實現,而沒有下放給子類來實現 - 條件變數的支援:
Sync
實現了AQS中的isHeldExclusively
方法(該方法會被AQS中的ConditionObject
的signal方法呼叫),並提供了newCondition
方法建立條件變數 - 獲取鎖資訊的方法:
Sync
提供了getOwner
、getHoldCount
、isLocked
三個方法用於獲取鎖的資訊,外圍類ReentrantLock
的三個同名方法會委託這三個方法來執行
獲取鎖
要利用AQS實現獲取鎖的功能,需要實現tryAcquire
方法。但是由於公平模式和非公平模式下獲取鎖的邏輯不同,因此tryAcquire
交給兩個子類去實現,Sync
並不實現
但是對於非公平獲取鎖的模式,NonFairSync
子類實現的tryAcquire
方法實際上委託了Sync
類的nonfairTryAcquire
方法來處理。nonfairTryAcquire
原始碼分析放在後面的非公平模式去講解
釋放鎖
要利用AQS實現釋放鎖的功能,需要實現tryRelease
方法。不同於獲取鎖,對於公平模式和非公平模式來說,釋放鎖的邏輯是相同的,因此tryRelease
的實現直接交給Sync
這一層來實現,而沒有下放給子類來實現
tryRelease
是嘗試釋放資源,而在ReentrantLock
中的語義環境下就是嘗試釋放鎖。其原始碼如下:
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; // 返回鎖是否空閒,如果空閒則為true
}
tryRelease
的releases
引數說明:
由於每次只釋放一個鎖,所以呼叫lock
釋放鎖時tryRelease
的releases
引數恆為1
但是ReentrantLock
支援條件變數,條件變數的await
方法也會呼叫tryRelease
方法一次性釋放所有的鎖資源,此時tryRelease
的引數releases
不一定為1
AQS中的release
方法會呼叫tryRelease
方法並接收其返回值,如下:
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
返回true,說明鎖為空閒,那麼就需要喚醒等待獲取鎖而阻塞,且等待最久的執行緒,讓它來獲取鎖。因此release
會喚醒同步佇列的隊首執行緒。如果鎖不是空閒,就不需要喚醒任何執行緒
版權:本文版權歸作者和部落格園共有
轉載:歡迎轉載,但未經作者同意,必須保留此段宣告;必須在文章中給出原文連線;否則必究法律責任
非公平鎖
非公平鎖是藉助Sync
和其子類NonfairSync
來實現的,實現了Sync
定義的lock
抽象方法,以及實現了AQS中的tryAcquire
方法以獲取鎖。原始碼如下:
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
// 實現了Sync中定義的lock方法
final void lock() {
// 上來就CAS,一點也不客氣————非公平性
if (compareAndSetState(0, 1)) // 如果state為0,說明鎖是空閒的,直接CAS獲取
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1); // 如果鎖不空閒,或者CAS競爭失敗,就呼叫acquire去獲取1個鎖,可能會被阻塞
}
// 非公平競爭鎖,實際上委託Sync.nonfairTryAcquire來執行
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
lock
方法先直接CAS修改state
,如果鎖空閒且修改成功,則說明獲取到了鎖,這裡也體現出非公平性,因為它不會謙讓已經在同步佇列中等待的執行緒
如果鎖非空閒或者競爭失敗,則會呼叫acquire
方法。acquire
會呼叫非公平鎖實現的tryAcquire
方法,再次進行競爭,可能直接獲取到鎖,也可能再次失敗,進入同步佇列阻塞等待,這裡同樣體現了非公平性
版權:本文版權歸作者和部落格園共有
轉載:歡迎轉載,但未經作者同意,必須保留此段宣告;必須在文章中給出原文連線;否則必究法律責任
公平鎖
公平鎖是藉助Sync
和其子類FairSync
來實現的,實現了Sync
定義的lock
抽象方法,以及實現了AQS中的tryAcquire
方法以獲取鎖。原始碼如下:
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) {
// 先檢查有無排隊等待的執行緒,如果有就不去CAS競爭——公平性
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;
}
}
lock
方法直接呼叫acquire
方法獲取鎖,而acquire
會呼叫非公平鎖實現的tryAcquire
方法,而tryAcquire
也遵循公平性,因此該lock方法整體上就是公平的
tryAcquire
方法會檢查鎖是否空閒,如果空閒,也不會立即去CAS爭奪,而是呼叫AQS的hasQueuedPredecessors
方法檢查是否有執行緒在同步佇列中等待,如果沒有才會CAS競爭。如果有就說明不能競爭,返回false
AQS中的hasQueuedPredecessors
方法會檢查是否有執行緒在同步佇列中等待,原始碼如下:
public final boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
// 如果head等於tail,說明是空佇列
// 如果隊首的thread域不是當前執行緒,說明有別的執行緒先於當前執行緒等待獲取鎖
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
要使用公平模式的鎖,需要將`ReentrantLock`的構造引數`fair`設為true:
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
版權:本文版權歸作者和部落格園共有
轉載:歡迎轉載,但未經作者同意,必須保留此段宣告;必須在文章中給出原文連線;否則必究法律責任
Lock介面的實現
ReentrantLock
實現了Lock
介面的所有方法,如下:
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(1);
}
public Condition newCondition() {
return sync.newCondition();
}
可以看到,ReentrantLock
實現的所有Lock
方法其實都是委託給了Sync
(AQS)來執行
版權:本文版權歸作者和部落格園共有
轉載:歡迎轉載,但未經作者同意,必須保留此段宣告;必須在文章中給出原文連線;否則必究法律責任
相關面試題
ReentrantLock
是如何實現可重入的
無論是公平鎖還是非公平鎖,獲取鎖呼叫tryAcquire
方法時,獲取成功後都會設定當前持有鎖的執行緒是自己。如果再次獲取該鎖,當發現鎖已經被持有時,會判斷持有鎖的執行緒是否是自己,如果是就可以不用競爭而直接獲取鎖
簡述非公平鎖和公平鎖之間的區別
-
從定義角度來說:
- 公平鎖:獲取鎖的順序和請求鎖的順序是一致的,即誰申請得早(等待得久),誰就最先獲取鎖
- 非公平鎖:競爭鎖時,等待時間最長的執行緒和剛剛過來競爭鎖(不在阻塞同步佇列中)的執行緒都有可能獲取鎖,CPU時間片輪詢到哪個執行緒,哪個就能獲得鎖
-
從原始碼角度來說:
當鎖被佔用時,請求鎖的所有執行緒都會按照FIFO的順序在同步佇列中阻塞等待。在鎖被釋放的時候,如果是非公平鎖,則隊首執行緒和剛剛過來請求鎖而不在阻塞佇列中的執行緒,都可能獲得鎖。如果是公平鎖,就一定是隊首執行緒獲得鎖,剛剛過來請求鎖得執行緒會被加入同步佇列阻塞等待
-
從效率上來說:公平鎖效率低於非公平鎖,主要是兩方面的開銷
- 程式碼執行上的開銷:公平模式下會多執行一個方法,該方法用於判斷是否有其他執行緒正在同步佇列中等待
- 系統層面的開銷:公平模式下,隊首執行緒是阻塞的,所以必須先將隊首執行緒喚醒,這涉及到作業系統上下文切換的操作,開銷較大。而在非公平模式下,可能是剛剛過來請求鎖的執行緒獲得鎖,而該執行緒已經是喚醒狀態,不需要上下文切換
為什麼
ReentrantLock.lock
方法不能被其他執行緒中斷
因為lock
方法呼叫的是AQS中的acquire
方法,該方法忽略中斷。而acquire
方法又會呼叫acquireQueued
方法,該方法執行過程中如果有其他執行緒中斷了當前執行緒,只會將中斷記錄下來,不會響應中斷。如果鎖已經被獲取,那麼該執行緒需要被阻塞,阻塞呼叫的是LockSupport.unpark
方法,該方法接收到中斷訊號後,不會丟擲中斷異常,而是返回。返回之後又會進入acquireQueued
的迴圈,如果不是隊首,就重新被阻塞。所以整個過程都不會被其他執行緒中斷,只會將中斷記錄下來
ReentrantLock
與synchronized
之間的相同和不同點
相同點:
它們都是通過加鎖實現同步,而且都是阻塞式同步,而不是非阻塞式,即當一個執行緒獲取鎖後,其他執行緒再請求鎖就會失敗而被阻塞,等到鎖釋放才有機會被喚醒
不同點:
ReentrantLock
:是Java 5之後提供的API層面的互斥鎖;需要lock
、unlock
配合try、finally使用;支援定時獲取鎖功能;支援可中斷的加鎖方法lockInterruptibly
,在等待獲取鎖時響應中斷,會丟擲中斷異常synchronized
:是Java語言的關鍵字,通過JVM實現;使用便捷;不支援定時獲取鎖功能;synchronized
在等待獲取鎖時不響應中斷,不丟擲中斷異常,只記錄中斷狀態
公平鎖和非公平鎖的區別在哪裡?
- 公平鎖:先到臨界區的執行緒一定會比後到的先獲得鎖
- 非公平鎖:先到臨界區的執行緒不一定比後到的先獲得鎖
synchronized
加鎖是公平鎖還是非公平鎖?
synchronized
是非公平鎖,執行緒到達臨界區就直接CAS嘗試獲取鎖,如果失敗則升級為輕量級鎖,再不斷CAS請求鎖。當CAS失敗到達一定次數之後,升級為重量級鎖,放入monitor物件的佇列中阻塞等待。而且入隊之前也會先嚐試獲取鎖,獲取不到才進入等待佇列
因此,執行緒獲取synchronized
鎖都不會關心有沒有其他執行緒之前獲取過,所以synchronized
是非公平鎖
為什麼要設定前驅節點的狀態為
SIGNAL
?
為了表示前驅節點的後繼節點對應的執行緒需要被喚醒,就這麼簡單