前言
本文通過可重入鎖ReentrantLock的源碼分析,加深對aqs和ReentrantLock的理解
關於AQS相關的知識可以參考我的另一篇文章Java併發——AQS源碼解析
先從使用上入手
構造方法
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
可以看到初始化了一個sync對象,而sync繼承了aqs
abstract static class Sync extends AbstractQueuedSynchronizer
默認初始化的是非公平鎖,構造方法傳遞true即爲公平鎖
先以默認的非公平鎖爲例,後面再總結公平鎖
加鎖
public void lock() {
sync.lock();
}
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
if (compareAndSetState(0, 1))
// 加鎖成功,設置當前持有鎖的線程
setExclusiveOwnerThread(Thread.currentThread());
else
// aqs的模板方法
// 加鎖失敗,說明其他線程先持有了鎖,acquire獲取鎖或者加入等待隊列
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
非公平鎖tryAcquire
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// 狀態爲0說明還沒有線程持有鎖
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 當前線程已經持有鎖
else if (current == getExclusiveOwnerThread()) {
// 重入鎖,狀態 +1
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
上面兩個條件都不滿足,直接返回false,進入等待隊列
釋放鎖
public void unlock() {
sync.release(1);
}
protected final boolean tryRelease(int releases) {
// 狀態減1
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
// 是否釋放鎖的標誌
boolean free = false;
// 狀態爲0,則清空當前線程信息,標誌位置爲0
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
// 否則更新狀態,返回false
setState(c);
return free;
}
因爲是可重入鎖,同一個線程,每次加鎖狀態+1,釋放鎖,狀態-1,這也就是爲什麼對於同一個線程,lock和unlock調用的次數一定要一致
超時和響應中斷
aqs提供了超時和響應中斷的功能,ReentrantLock也提供了對應的方法
超時
public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
// 直接調用aqs提供的方法
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
線程等待超時或被中斷,方法會立即返回
響應中斷
public void lockInterruptibly() throws InterruptedException {
// 同樣也是直接調用aqs提供的獲取鎖響應中斷方法
sync.acquireInterruptibly(1);
}
線程被中斷會方法立即返回
關於aqs這兩個方法的具體分析可以參考文章開頭提到的AQS源碼分析
newCondition
對於ReentrantLock我們通常也會使用其condition對象進行線程間的通信
作用類似於wait() 和 notify()
final ConditionObject newCondition() {
return new ConditionObject();
}
newCondition初始化了AQS中的ConditionObject對象
ConditionObject有如下兩個屬性,我們可以知道,其內部也是維護了一個鏈表
/** First node of condition queue. */
private transient Node firstWaiter;
/** Last node of condition queue. */
private transient Node lastWaiter;
直接從我們最常用的await和signal方法入手分析
ConditionObject#await
後續會涉及到兩個隊列,提前有個概念
- sync同步隊列:指的是AQS中等待獲取鎖的node隊列
- condition隊列:指的是調用condition#await方法,等待signal喚醒的node隊列
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
// 添加到condition隊列
Node node = addConditionWaiter();
// await方法要完全釋放鎖
int savedState = fullyRelease(node);
int interruptMode = 0;
// 如果不在sync隊列(signal方法會將node放到sync隊列中),則阻塞線程
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
// 線程被喚醒,重新獲取鎖
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
然後我們按照方法細節再進行分析
ConditionObject#addConditionWaiter
添加線程到condition隊列
private Node addConditionWaiter() {
Node t = lastWaiter;
// 如果尾節點取消的話,則移出隊列,獲取最後一個未取消的節點
// If lastWaiter is cancelled, clean out.
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
// 第一次調用await會走到這裏,node狀態爲condition,表示在condition隊列
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
// 從頭到尾遍歷,移除已經取消的node節點
private void unlinkCancelledWaiters() {
Node t = firstWaiter;
Node trail = null;
while (t != null) {
Node next = t.nextWaiter;
if (t.waitStatus != Node.CONDITION) {
t.nextWaiter = null;
if (trail == null)
firstWaiter = next;
else
trail.nextWaiter = next;
if (next == null)
lastWaiter = trail;
}
else
trail = t;
t = next;
}
}
假設此時有三個線程調用了condition的await方法阻塞,等待喚醒,那麼此時condition隊列的狀態如下
看到與aqs同步隊列的區別沒
- 沒有空的head節點,firstWaiter代表第一個節點,lastWaiter代表最後一個節點
- waitStatus爲Node.CONDITION
- 雖然同樣是Node對象,但是並沒有賦值prev和next屬性,實際上是單向鏈表,用nextWaiter連接
ConditionObject#fullyRelease
完全釋放鎖,調用await方法,就代表要完全釋放當前持有的鎖(可重入)
final int fullyRelease(Node node) {
boolean failed = true;
try {
// 獲取當前鎖狀態
int savedState = getState();
// 完全釋放鎖
if (release(savedState)) {
failed = false;
return savedState;
} else {
throw new IllegalMonitorStateException();
}
} finally {
if (failed)
node.waitStatus = Node.CANCELLED;
}
}
ConditionObject#isOnSyncQueue
判斷node是否在同步隊列中,如果在同步隊列則不阻塞線程
final boolean isOnSyncQueue(Node node) {
// 如果狀態爲condition或者前驅節點爲null,則一定在condition隊列中
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
// 前後節點都有了,那一定在同步隊列了
if (node.next != null) // If has successor, it must be on queue
return true;
/*
* node.prev can be non-null, but not yet on queue because
* the CAS to place it on queue can fail. So we have to
* traverse from tail to make sure it actually made it. It
* will always be near the tail in calls to this method, and
* unless the CAS failed (which is unlikely), it will be
* there, so we hardly ever traverse much.
*/
return findNodeFromTail(node);
}
// 在aqs同步隊列中,從tail開始向前找node,找到則返回true
private boolean findNodeFromTail(Node node) {
Node t = tail;
for (;;) {
if (t == node)
return true;
if (t == null)
return false;
t = t.prev;
}
}
如此看來,只有prev存在而next爲null的時候,纔會走到最後findNodeFromTail
註釋的意思是說prev不爲空,不能代表已經在同步隊列中,因爲會通過CAS將自己設置爲tail,可能會失敗,再回顧一下入隊操作
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
// 這裏將prev指向之前的tail,prev已經不爲空了
node.prev = t;
// cas將自己設置爲tail,這裏成功了纔是入隊列成功
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
阻塞前的方法都分析完成,接下來先分析下signal方法,便於更好的理解await方法
ConditionObject#signal
喚醒condition隊列第一個節點(未取消的)
public final void signal() {
// 判斷是否持有鎖
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
// 第一個節點不爲空,執行通知
if (first != null)
doSignal(first);
}
private void doSignal(Node first) {
do {
// 如果只有一個節點,將lastWaiter置爲null
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
// 隊列中移除第一個節點
first.nextWaiter = null;
// 如果node轉換成功,則結束循環,否則繼續往後進行喚醒
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
// 將node從condition隊列中轉換到同步隊列中
final boolean transferForSignal(Node node) {
// 修改狀態爲0,如果失敗說明被取消,返回false接着向後找
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
// 加入同步隊列,返回前驅節點p
Node p = enq(node);
int ws = p.waitStatus;
// 如果前驅節點狀態大於0(已取消)或者CAS設置狀態爲SIGNAL失敗,則直接喚醒線程
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
當一個線程node在condition隊列中await時,如果被其他線程signal喚醒,那麼該node就需要加入aqs同步隊列中等待再次獲取鎖,用來執行await之後的代碼
所以signal方法概括來說就是,將condition隊列中的第一個節點移除,並將其加入aqs同步隊列中,用圖來表示會更直觀
這裏最令人迷惑的就是transferForSignal方法
最最迷惑的就是該方法中最後的條件判斷,喚醒操作
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
這個操作首先是node加入aqs同步隊列後,判斷它的前驅節點,也就是上圖轉移到aqs同步隊列之後的node-1的狀態
- 如果 > 0 就直接喚醒Thread-1(剛轉移到同步隊列的線程)
- 如果 <= 0,但是cas設置其狀態爲signal時失敗,也直接喚醒Thread-1
思考了好久纔有眉目,這裏爲什麼滿足這兩個條件要提前喚醒呢?
從正確性上來看,即使這裏不喚醒也不會出問題,因爲調用signal的線程一定會調用unlock方法,而unlock方法就會喚醒aqs同步隊列中的第一個非取消node,所以最終一定會傳遞下去,喚醒剛剛加入隊尾的node-2(Thread-1)
那我們回頭繼續看下await方法被喚醒之後的操作
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
// 添加到condition隊列
Node node = addConditionWaiter();
// await方法要完全釋放鎖
int savedState = fullyRelease(node);
int interruptMode = 0;
// 如果不在sync隊列(signal方法會將node放到sync隊列中),則阻塞線程
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
// 線程被喚醒,重新獲取鎖
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
// 還有其他節點的話,遍歷清除取消的節點
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
// 中斷後處理
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
被喚醒之後,加入aqs同步隊列,跳出while循環,調用acquireQueued方法
該方法可以看aqs源碼回憶一下,主要就這幾個步驟
- 如果是第一個等待的節點,就嘗試獲取鎖
- 如果不是,則會去判斷前驅節點的狀態,如果取消則刪除,向前找非取消的節點
- 確保前驅節點未取消,且狀態爲signal,則將當前線程阻塞
如果transferForSignal中,判斷前驅節點已經取消,或者無法設置爲signal狀態,不提前喚醒
那麼等調用signal方法的線程unlock之後,去喚醒aqs同步隊列的節點
當一直喚醒到transferForSignal中轉移的節點之前時,還是要執行acquireQueued方法
處理前驅取消的節點,再設置狀態,而acquireQueued方法的調用是不需要持有獨佔鎖的
所以這裏爲了提高併發性能,讓acquireQueued方法和調用signal之後的操作同時進行
多看幾遍源碼就能理解,其實就是讓如下兩段代碼併發執行
喚醒之後對中斷異常的處理
private int checkInterruptWhileWaiting(Node node) {
return Thread.interrupted() ?
(transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
0;
}
final boolean transferAfterCancelledWait(Node node) {
if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
enq(node);
return true;
}
/*
* If we lost out to a signal(), then we can't proceed
* until it finishes its enq(). Cancelling during an
* incomplete transfer is both rare and transient, so just
* spin.
*/
while (!isOnSyncQueue(node))
Thread.yield();
return false;
}
1、如果阻塞過程中線程沒被中斷,則返回0,且加入同步隊列中,退出循環
2、被中斷則修改狀態爲0,並加入到aqs同步隊列中
3、設置狀態失敗會自旋,直到加入到aqs同步隊列中
這裏返回的中斷模式有2種,主要是用來做不同的中斷後處理
/** Mode meaning to reinterrupt on exit from wait */
private static final int REINTERRUPT = 1;
/** Mode meaning to throw InterruptedException on exit from wait */
private static final int THROW_IE = -1;
/**
* Throws InterruptedException, reinterrupts current thread, or
* does nothing, depending on mode.
*/
private void reportInterruptAfterWait(int interruptMode)
throws InterruptedException {
if (interruptMode == THROW_IE)
throw new InterruptedException();
else if (interruptMode == REINTERRUPT)
selfInterrupt();
}
第1種情況是在喚醒後中斷,喚醒後狀態會被設置爲0,所以cas會失敗,自旋就是在等待加入aqs同步隊列之後再返回,否則await方法的循環會退出,可能還沒有調用enq方法入隊
Thread.interrupted()會清除中斷狀態,喚醒後重新interrupt
第2種情況就是在喚醒前中斷,狀態一定是CONDITION,最後返回-1
阻塞時中斷,拋出中斷異常
公平鎖
與非公平鎖的區別
lock
// 公平鎖
final void lock() {
acquire(1);
}
// 非公平鎖
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
首先從lock方法我們可以看到,公平鎖的區別就是每次獲取鎖都直接調用acquire去排隊,而非公平鎖,先嚐試cas獲取鎖,如果競爭到就持有鎖,不管隊列是否有其他等待的線程
tryAcquire
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;
}
如果當前沒有線程持有鎖,那麼要先判斷隊列中有沒有其他線程在等待獲取,不能直接嘗試獲取鎖
public final boolean hasQueuedPredecessors() {
// The correctness of this depends on head being initialized
// before tail and on head.next being accurate if the current
// thread is first in queue.
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
h != t 說明有其他等待節點
(s = h.next) == null 這個條件什麼時候成立呢?
頭尾不相等,但是頭的next爲空,那就只有一種情況就是某個節點正在加入隊列過程中
還記得enq入隊方法麼?
Node t = tail;
...
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
先將node的prev指向原來的tail,再通過cas更新tail引用,最後纔將前一個節點的next連起來
所以在t.next = node;執行前,cas成功後,會出現頭的next爲空
這種情況就直接返回true
如果不是的話,那麼需要判斷是不是當前線程在等待
總結
ReentrantLock源碼分析就結束了,回顧總結一下
- ReentrantLock是jdk層面提供的獨佔鎖,基於AQS實現
- 提供了公平與非公平兩種方式
- 公平:lock時要判斷前面有沒有其他線程在等待,有先後順序
- 非公平:lock時直接嘗試cas競爭鎖,不管前面是否有其他線程等待
- 可重入鎖,同一個線程lock和unlock方法調用次數一定要對應,保證正確釋放鎖
- 通過AQS的ConditionObject對象,提供了線程間通信的方法
- await時加入condition隊列,釋放鎖
- signal時喚醒節點,從condition隊列轉移到aqs同步隊列中,重新競爭鎖
- 基於AQS,同樣的提供了響應中斷,獲取鎖超時方法