JUC-AQS-Condition源碼詳解
如果直接看這個有難度,大家可以看下基礎的AQS詳解JUC(一)-AQS源碼分析
一、Condition的作用
二、Condition的數據結構
三、核心源碼解讀
3.1 await()
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();
//放棄所有持有的鎖
int savedState = fullyRelease(node);
int interruptMode = 0;
//阻塞判斷,當前node是不是在同步隊列時,不在同步隊列那麼就park當前線程
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
//如果被中斷了,則檢測中斷
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
//這是此節點已經在同步隊列中了
//在隊列中獲取鎖,並判斷當前的interruptMode不爲-1,即不是拋出異常
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
//把中斷類型設置爲,重新中斷,意味在線程獲得鎖的時候,重新中斷線程
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
1.先判斷當前線程的中斷狀態,如果爲中斷則拋出異常
2.如果不是中斷狀態,則把當前節點加入Condition隊列中,即我們的等待隊列中
3.釋放當前持有的鎖,並記住當前state的值
4.會阻塞在判斷當前節點是否在同步隊列中,只有當執行了signal(),節點纔會被添加回到同步隊列中,如果不在隊列中,則park當前線程
5.如果在同步隊列中,則嘗試從隊列中獲得鎖
6.獲得鎖之後,需要響應不同的中斷模式
/**
* 判斷是否要拋出中斷異常
*/
private void reportInterruptAfterWait(int interruptMode)
throws InterruptedException {
//如果中斷模型爲THROW_IE,就拋出異常
if (interruptMode == THROW_IE)
throw new InterruptedException();
//如果中斷模型爲REINTERRUPT,就只把線程標記爲中斷
else if (interruptMode == REINTERRUPT)
selfInterrupt();
}
/**
* Convenience method to interrupt current thread.
* 中斷當前線程
*/
static void selfInterrupt() {
Thread.currentThread().interrupt();
}
主流程講過了,我們重點看下第二步Node node = addConditionWaiter()
1.獲取當前Condition的lastWaiter
2.判斷lastWaiter的狀態,是否爲Cancelled
3.如果爲Cancelled則處理當前Condition中的節點,清除爲Cancelled的節點,並設置新的lastWaiter
4.如果不爲Cancelled,則用當前線程新建一個狀態爲Condition的Node節點
5.判斷t(lastWaiter)是否爲null,如果爲null,則代表當前等待隊列中已經沒有值了,所以我們把firstWaiter設置爲新建的node
6.如果t不爲null,那我們就可以把t.nextWaiter設置爲node
7.將lastWaiter設置爲node
以上邏輯我們可以通過簡單的類型來劃分一下
上圖是從分類角度來闡述不同場景下的不同處理邏
3.2 signal()
public final void signal() {
//判斷是否爲當前線程是否爲佔有此鎖的線程
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
//獲得到當前的第一個等待節點
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
具體流程
1.先判斷是否爲當前線程是否爲佔有此鎖的線程
2.獲得此等待隊列中第一個等待節點,如果此節點不爲null,則doSignal此節點
下面我們具體看看doSignal()方法的實現
1.先判斷頭結點的下一個節點是否爲null,如果爲null的話,設置lastWaiter爲null
2.如果不爲null的話,因爲頭節點已經獲取出來了,自然要把他的nextWaiter設置爲null
3.根據當前節點是否可以從Condition狀態設置到0(node的初始化狀態),把當前節點轉移到同步隊列中去。
4.判斷在同步隊列中前節點的狀態,判斷是否要unpark()當前線程。
//刪除或者轉移節點到同步隊列中,直到獲取的節點是不可取消節點,或者null
private void doSignal(Node first) {
do {
//判斷當前節點的下一個節點是否爲null
if ( (firstWaiter = first.nextWaiter) == null)
//如果爲null,則證明此等待隊列無數據,把lastWaiter也設置爲null
lastWaiter = null;
//first節點需要取出,所以nextWaiter設置爲null
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
//將節點從條件隊列,轉送到同步隊列,如果成功的話,返回true
final boolean transferForSignal(Node node) {
/*
* 如果無法改變waiStatus的值,那麼當前節點已經被取消
*/
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
//把節點插入到隊列中,返回node的上一個節點
Node p = enq(node);
int ws = p.waitStatus;
//前節點的狀態>0,即爲Cancelled狀態,或者前節點的狀態設置爲signal失敗
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
//喚醒當前線程
LockSupport.unpark(node.thread);
return true;
}
四、Condition和AQS的關係
Condition是條件隊列,那麼怎麼做到的呢?最終獲取鎖,還是在同步隊列中,那麼如何做到讓執行中的線程,退出擁有的鎖,並等待喚醒呢?java是這麼做的,直接搞一個同步隊列,保存那些等待喚醒的線程。
await()
head執行await,當前head,生成Condition狀態的Node節點,並添加到條件隊列中。在條件隊列中的node是無法獲得到鎖的,所以需要一直檢測,此節點是否被移動到同步隊列中,如果檢測到移動到隊列中,便會嘗試獲得鎖,就可以繼續執行了。
signal()
指定doSignal(firstWaiter)
1.先把當前firstWaiter從條件隊列中完整剝離出來
2.將節點追加到同步隊列的tail後面
3.處理條件節點的隊列,將firstWaiter的指針指向下一個節點
4.判斷同步隊列中的前一個節點的狀態,判斷是否可以unpark()當前線程
五、實戰
源代碼如下,大家可以自行debug,我只取幾個典型的場景,給大家說明一下
public class MyCondition {
private static Lock lock = new ReentrantLock();
private static Condition A = lock.newCondition();
private static Condition B = lock.newCondition();
private static int count = 0;
static class ThreadA extends Thread{
@Override
public void run() {
this.setName("ThreadA");
try {
lock.lock();
System.out.println("A加鎖");
for (int i = 0; i < 10; i++) {
while (count%2 != 0){
//第一次斷點
System.out.println("A.await()之前");
A.await();
System.out.println("A等待之後");
}
System.out.println("A");
count++;
B.signal();
System.out.println("喚醒B");
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
static class ThreadB extends Thread{
@Override
public void run() {
this.setName("ThreadB");
try {
lock.lock();
System.out.println("B加鎖");
for (int i = 0; i < 10; i++) {
while (count%2 != 1){
B.await();
System.out.println("B等待");
}
System.out.println("B");
//第二次斷點
count++;
A.signal();
//第三次斷點
System.out.println("喚醒A");
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
public static void main(String[] args) {
new MyCondition.ThreadA().start();
new MyCondition.ThreadB().start();
}
}
1. 在執行A.await()之前的數據
1.當前持有鎖的是ThreadA
2.同步隊列中保存着ThreadB
3.A等待隊列中無數據
2. A執行await()方法之後,在A.signal()之前,我們看下鎖的數據
1.當前持有鎖的是ThreadB
2.同步隊列的tail數據爲空
3.A等待隊列的頭結點爲ThreadA
3.當執行喚醒A之後
1.可以看到當前獲得鎖的線程是ThreadB
2.ThreadA已經被加入到同步隊列中了