主頁 > 軟體設計 > 資深大廠JAVA架構師帶你剖析Condition原始碼

資深大廠JAVA架構師帶你剖析Condition原始碼

2020-09-15 21:08:20 軟體設計

點關注,不迷路!如果本文對你有幫助的話不要忘記點贊支持哦!

1. Condition 定義

Condition是JUC里面提供于控制執行緒釋放鎖, 然后進行等待其他獲取鎖的執行緒發送 signal 信號來進行喚醒的工具類.
主要特點:

  1. Condition內部主要是由一個裝載執行緒節點 Node 的 Condition Queue 實作
  2. 對 Condition 的方法(await, signal等) 的呼叫必需是在本執行緒獲取了獨占鎖的前提下
  3. 因為 操作Condition的方法的前提是獲取獨占鎖, 所以 Condition Queue 內部是一條不支持并發安全的單向 queue (這是相對于 AQS 里面的 Sync Queue)

先看一下一個常見的 demo

import org.apache.log4j.Logger;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 此demo用于測驗 condition
 * Created by xujiankang on 2017/2/8.
 */
public class ConditionTest {

    private static final Logger logger = Logger.getLogger(ConditionTest.class);

    static final Lock lock = new ReentrantLock();
    static final Condition condition = lock.newCondition();

    public static void main(String[] args) throws Exception{

        final Thread thread1 = new Thread("Thread 1 "){
            @Override
            public void run() {
                lock.lock(); // 執行緒 1獲取 lock
                logger.info(Thread.currentThread().getName() + " 正在運行 .....");

                try {
                    Thread.sleep(2 * 1000);
                    logger.info(Thread.currentThread().getName() + " 停止運行, 等待一個 signal ");
                    condition.await(); // 呼叫 condition.await 進行釋放鎖, 將當前節點封裝成一個 Node 放入 Condition Queue 里面, 等待喚醒
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                logger.info(Thread.currentThread().getName() + " 獲取一個 signal, 繼續執行 ");
                lock.unlock(); // 釋放鎖
            }
        };

        thread1.start();  // 執行緒 1 線運行

        Thread.sleep(1 * 1000);

        Thread thread2 = new Thread("Thread 2 "){
            @Override
            public void run() {
                lock.lock();        // 執行緒 2獲取lock
                logger.info(Thread.currentThread().getName() + " 正在運行.....");
                thread1.interrupt(); // 對執行緒1 進行中斷 看看中斷后會怎么樣? 結果 執行緒 1還是獲取lock, 并且最后還進行 lock.unlock()操作

                try {
                    Thread.sleep(2 * 1000);
                }catch (Exception e){

                }
                condition.signal(); // 發送喚醒信號 從 AQS 的 Condition Queue 里面轉移 Node 到 Sync Queue
                logger.info(Thread.currentThread().getName() + " 發送一個 signal ");
                logger.info(Thread.currentThread().getName() + " 發送 signal 結束");
                lock.unlock(); // 執行緒 2 釋放鎖
            }
        };

        thread2.start();

    }


}

整個執行步驟

  1. 執行緒 1 開始執行, 獲取 lock, 然后開始睡眠 2秒
  2. 當執行緒1睡眠到 1秒時, 執行緒2開始執行, 但是lock被執行緒1獲取, 所以 等待
  3. 執行緒 1 睡足2秒 呼叫 condition.await() 進行鎖的釋放, 并且將 執行緒1封裝成一個 node 放到 condition 的 Condition Queue里面, 等待其他獲取鎖的執行緒給他 signal, 或對其進行中斷(中斷后可以到 Sync Queue里面進而獲取 鎖)
  4. 執行緒 2 獲取鎖成功, 中斷 執行緒1, 執行緒被中斷后, node 從 Condition Queue 轉移到 Sync Queue 里面, 但是 lock 還是被 執行緒2獲取者, 所以 node呆在 Sync Queue 里面等待獲取 lock
  5. 執行緒 2睡了 2秒, 開始 用signal喚醒 Condition Queue 里面的節點(此時代表 執行緒1的node已經到 Sync Queue 里面)
  6. 執行緒 2釋放lock, 并且在 Sync Queue 里面進行喚醒等待獲取鎖的節點 node
  7. 執行緒1 得到喚醒, 獲取鎖
  8. 執行緒1 釋放鎖

執行結果

[2017-02-08 22:43:09,557] INFO  Thread 1  (ConditionTest.java:26) - Thread 1  正在運行 .....
[2017-02-08 22:43:11,565] INFO  Thread 1  (ConditionTest.java:30) - Thread 1  停止運行, 等待一個 signal 
[2017-02-08 22:43:11,565] INFO  Thread 2  (ConditionTest.java:48) - Thread 2  正在運行.....
java.lang.InterruptedException
[2017-02-08 22:43:13,566] INFO  Thread 2  (ConditionTest.java:57) - Thread 2  發送一個 signal 
    at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.reportInterruptAfterWait(AbstractQueuedSynchronizer.java:2014)
[2017-02-08 22:43:13,566] INFO  Thread 2  (ConditionTest.java:58) - Thread 2  發送 signal 結束
    at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2048)
[2017-02-08 22:43:13,567] INFO  Thread 1  (ConditionTest.java:35) - Thread 1  獲取一個 signal, 繼續執行 
    at com.lami.tuomatuo.search.base.concurrent.aqs.ConditionTest$1.run(ConditionTest.java:31)

2. Condition 建構式級基本屬性

主要是Condition Queue 的頭尾節點(這里頭尾節點不需要進行初始化)

/** First node of condition queue */
/** Condition Queue 里面的頭節點 */
private transient Node firstWaiter;
/** Last node of condition queue */
/** Condition Queue 里面的尾節點 */
private transient Node lastWaiter;

/** Creates a new {@code ConditionObject} instance */
/** 建構式 */
public ConditionObject(){}

3. Condition Queue enqueue節點方法 addConditionWaiter

addConditionWaiter方法主要用于呼叫 Condition.await 時將當前節點封裝成 一個Node, 加入到 Condition Queue里面

大家可以注意下, 下面對 Condition Queue 的操作都沒考慮到 并發(Sync Queue 的佇列是支持并發操作的), 這是為什么呢? 因為在進行操作 Condition 是當前的執行緒已經獲取了AQS的獨占鎖, 所以不需要考慮并發的情況

/**
 * Adds a new waiter to wait queue
 * 將當前執行緒封裝成一個 Node 節點 放入大 Condition Queue 里面
 * 大家可以注意到, 下面對 Condition Queue 的操作都沒考慮到 并發(Sync Queue 的佇列是支持并發操作的), 這是為什么呢? 因為在進行操作 Condition 是當前的執行緒已經獲取了AQS的獨占鎖, 所以不需要考慮并發的情況
 * @return
 */
private Node addConditionWaiter(){
    Node t = lastWaiter;                                // 1. Condition queue 的尾節點
    // If lastWaiter is cancelled, clean out              // 2.尾節點已經Cancel, 直接進行清除,
                                                          //    這里有1個問題, 1 何時出現t.waitStatus != Node.CONDITION -> 在對執行緒進行中斷時 ConditionObject -> await -> checkInterruptWhileWaiting -> transferAfterCancelledWait "compareAndSetWaitStatus(node, Node.CONDITION, 0)" <- 導致這種情況一般是 執行緒中斷或 await 超時
                                                          //    一個注意點: 當Condition進行 awiat 超時或被中斷時, Condition里面的節點是沒有被洗掉掉的, 需要其他 await 在將執行緒加入 Condition Queue 時呼叫addConditionWaiter而進而洗掉, 或 await 操作差不多結束時, 呼叫 "node.nextWaiter != null" 進行判斷而洗掉 (PS: 通過 signal 進行喚醒時 node.nextWaiter 會被置空, 而中斷和超時時不會)
    if(t != null && t.waitStatus != Node.CONDITION){
        unlinkCancelledWaiters();                        // 3. 呼叫 unlinkCancelledWaiters 對 "waitStatus != Node.CONDITION" 的節點進行洗掉(在Condition里面的Node的waitStatus 要么是CONDITION(正常), 要么就是 0 (signal/timeout/interrupt))
        t = lastWaiter;                                // 4. 獲取最新的 lastWaiter
    }
    Node node = new Node(Thread.currentThread(), Node.CONDITION); // 5. 將執行緒封裝成 node 準備放入 Condition Queue 里面
    if(t == null){
        firstWaiter = node;                           // 6 .Condition Queue 是空的
    }else{
        t.nextWaiter = node;                          // 7. 最加到 queue 尾部
    }
    lastWaiter = node;                                // 8. 重新賦值 lastWaiter
    return node;
}

4. Condition 喚醒 first節點方法 doSignal

這里的喚醒指的是將節點從 Condition Queue 轉移到 Sync Queue 里面

/**
 * Removes and transfers nodes until hit non-cancelled one or
 * null. Split out from signal in part to encourage compilers
 * to inline the case of no waiters
 * @param first
 */
/**
 * 喚醒 Condition Queue 里面的頭節點, 注意這里的喚醒只是將 Node 從 Condition Queue 轉到 Sync Queue 里面(這時的 Node 也還是能被 Interrupt)
 */
private void doSignal(Node first){
    do{
        if((firstWaiter = first.nextWaiter) == null){ // 1. 將 first.nextWaiter 賦值給 nextWaiter 為下次做準備
            lastWaiter = null;                          // 2. 這時若 nextWaiter == null, 則說明 Condition 為空了, 所以直接置空 lastWaiter
        }
        first.nextWaiter = null;                        // 3.  first.nextWaiter == null 是判斷 Node 從 Condition queue 轉移到 Sync Queue 里面是通過 signal 還是 timeout/interrupt
    }while(!transferForSignal(first) && (first = firstWaiter) != null); // 4. 呼叫  transferForSignal將 first 轉移到 Sync Queue 里面, 回傳不成功的話, 將 firstWaiter 賦值給 first
}

5. Condition 喚醒 所有 節點方法 doSignalAll

/**
 * Removes and transfers all nodes
 * @param first (non-null) the first node on condition queue
 */
/**
 * 喚醒 Condition Queue 里面的所有的節點
 */
private void doSignalAll(Node first){
    lastWaiter = firstWaiter = null;       // 1. 將 lastWaiter, firstWaiter 置空
    do{
        Node next = first.nextWaiter;        // 2. 初始化下個換新的節點
        first.nextWaiter = null;            // 3.  first.nextWaiter == null 是判斷 Node 從 Condition queue 轉移到 Sync Queue 里面是通過 signal 還是 timeout/interrupt
        transferForSignal(first);             // 4. 呼叫  transferForSignal將 first 轉移到 Sync Queue 里面
        first = next;                         // 5. 開始換新 next 節點
    }while(first != null);
}

6. Condition 洗掉取消節點的方法 unlinkCancelledWaiters

一般的節點都會被 signal 喚醒, 從 Condition Queue 轉移到 Sync Queue, 而若遇到 interrupt 或 等待超時, 則直接改變 node 的狀態(從 CONDITION 變成 0), 并直接放入 Sync 里面, 而不清理Condition Queue 里面的節點, 所以需要下面的函式

/**
 * http://czj4451.iteye.com/blog/1483264
 *
 * Unlinks cancelled waiter nodes from condition queue
 * Called only while holding lock. This is called when
 * cancellation occured during condition wait, and upon
 * insertion of a new waiter when lastWaiter is seen to have
 * been cancelled. This method is needed to avoid garbage
 * retention in the absence of signals. So even though it may
 * require a full traversal, it comes intot play when
 * timeouts or cancellations all nodes rather than stoppping at a
 * particular target to unlink all pointers to garbege nodes
 * without requiring many re-traversals during cancellation
 * storms
 */
/**
 * 在 呼叫 addConditionWaiter 將執行緒放入 Condition Queue 里面時 或 awiat 方法獲取 差不多結束時 進行清理 Condition queue 里面的因 timeout/interrupt 而還存在的節點
 * 這個洗掉操作比較巧妙, 其中引入了 trail 節點, 可以理解為traverse整個 Condition Queue 時遇到的最后一個有效的節點
 */
private void unlinkCancelledWaiters(){
    Node t = firstWaiter;
    Node trail = null;
    while(t != null){
        Node next = t.nextWaiter;               // 1. 先初始化 next 節點
        if(t.waitStatus != Node.CONDITION){   // 2. 節點不有效, 在Condition Queue 里面 Node.waitStatus 只有可能是 CONDITION 或是 0(timeout/interrupt引起的)
            t.nextWaiter = null;               // 3. Node.nextWaiter 置空
            if(trail == null){                  // 4. 一次都沒有遇到有效的節點
                firstWaiter = next;            // 5. 將 next 賦值給 firstWaiter(此時 next 可能也是無效的, 這只是一個臨時處理)
            }else{
                trail.nextWaiter = next;       // 6. next 賦值給 trail.nextWaiter, 這一步其實就是洗掉節點 t
            }
            if(next == null){                  // 7. next == null 說明 已經 traverse 完了 Condition Queue
                lastWaiter = trail;
            }
        }else{
            trail = t;                         // 8. 將有效節點賦值給 trail
        }
        t = next;
    }
}

毫無疑問, 這是一段非常精巧的queue節點洗掉, 主要還是在 節點 trail 上, trail 節點可以理解為traverse整個 Condition Queue 時遇到的最后一個有效的節點

7. Condition 喚醒首節點方法 signal

/**
 * Moves the longest-waiting thread, if one exists, from the
 * wait queue for this condition to the wait queue for the
 * owning lock
 *
 * @throws IllegalMonitorStateException if{@link #isHeldExclusively()}
 *          returns {@code false}
 */
/**
 * 將 Condition queue 的頭節點轉移到 Sync Queue 里面
 * 在進行呼叫 signal 時, 當前的執行緒必須獲取了 獨占的鎖
 */
@Override
public void signal() {
    if(!isHeldExclusively()){       // 1. 判斷當前的執行緒是否已經獲取 獨占鎖
        throw new IllegalMonitorStateException();
    }
    Node first = firstWaiter;
    if(first != null){
        doSignal(first);           // 2. 呼叫 doSignal 進行轉移
    }
}

上述面試題答案都整理成檔案筆記, 也還整理了一些面試資料&最新2020收集的一些大廠的面試真題(都整理成檔案,小部分截圖),有需要的可以 點擊進入暗號:csdn ,

8. Condition 喚醒所有節點方法 signalAll

/**
 * Moves all threads from the wait queue for this condition to
 * the wait queue for the owning lock
 *
 * @throws IllegalMonitorStateException if {@link #isHeldExclusively()}
 *          return {@code false}
 */
/**
 * 將 Condition Queue 里面的節點都轉移到 Sync Queue 里面
 */
public final void signalAll(){
    if(!isHeldExclusively()){
        throw new IllegalMonitorStateException();
    }
    Node first = firstWaiter;
    if(first != null){
        doSignalAll(first);
    }
}

9. Condition 釋放鎖進行等待方法 awaitUninterruptibly

awaitUninterruptibly 方法是一個不回應 中斷的方法
整個流程

  1. 將當前的執行緒封裝成 Node 加入到 Condition 里面
  2. 丟到當前執行緒所擁有的 獨占鎖
  3. 等待 其他獲取 獨占鎖的執行緒的喚醒, 喚醒從 Condition Queue 到 Sync Queue 里面, 進而獲取 獨占鎖
  4. 最后獲取 lock 之后, 在根據執行緒喚醒的方式(signal/interrupt) 進行處理

/**
 * Implements uninterruptible condition wait
 * <li>
 *     Save lock state return by {@link #getState()}
 * </li>
 *
 * <li>
 *     Invoke {@link #release(int)} with saved state as argument,
 *     throwing IllegalMonitoringStateException if it fails
 *     Block until signalled
 *     Reacquire by invoking specified version of
 *     {@link #acquire(int)} with saved state as argument
 * </li>
 */
/**
 * 不回應執行緒中斷的方式進行 await
 */
public final void awaitUninterruptibly(){
    Node node = addConditionWaiter();       // 1. 將當前執行緒封裝成一個 Node 放入 Condition Queue 里面
    int savedState = fullyRelease(node);   // 2. 釋放當前執行緒所獲取的所有的獨占鎖(PS: 獨占的鎖支持重入), 等等, 為什么要釋放呢? 以為你呼叫 awaitUninterruptibly 方法的前提就是你已經獲取了 獨占鎖
    boolean interrupted = false;         // 3. 執行緒中斷標識
    while(!isOnSyncQueue(node)){          // 4. 這里是一個 while loop, 呼叫 isOnSyncQueue 判斷當前的 Node 是否已經被轉移到 Sync Queue 里面
       LockSupport.park(this);            // 5. 若當前 node 不在 sync queue 里面, 則先 block 一下等待其他執行緒呼叫 signal 進行喚醒; (這里若有其他執行緒對當前執行緒進行 中斷的換, 也能進行喚醒)
        if(Thread.interrupted()){         // 6. 判斷這是喚醒是 signal 還是 interrupted(Thread.interrupted()會清楚執行緒的中斷標記, 但沒事, 我們有步驟7中的interrupted進行記錄)
            interrupted = true;           // 7. 說明這次喚醒是被中斷而喚醒的,這個標記若是true的話, 在 awiat 離開時還要 自己中斷一下(selfInterrupt), 其他的函式可能需要執行緒的中斷標識
        }
    }
    if(acquireQueued(node, savedState) || interrupted){ // 8. acquireQueued 回傳 true 說明執行緒在 block 的程序中式被 inetrrupt 過(其實 acquireQueued 回傳 true 也有可能其中有一次喚醒是 通過 signal)
        selfInterrupt();                 // 9. 自我中斷, 外面的執行緒可以通過這個標識知道, 整個 awaitUninterruptibly 運行程序中 是否被中斷過
    }
}

10. Condition 中斷標示

下面兩個是用于追蹤 呼叫 awaitXXX 方法時執行緒有沒有被中斷過
主要的區別是
1. REINTERRUPT: 代表執行緒是在 signal 后被中斷的 (REINTERRUPT = re-interrupt 再次中斷 最后會呼叫 selfInterrupt)
2. THROW_IE: 代表在接受 signal 前被中斷的, 則直接拋出例外 (Throw_IE = throw inner exception)

/**
 * For interruptible waits, we need to track whether to throw
 * InterruptedException, if interrupted while blocked on
 * condition, versus reinterrupt current thread, if
 * interrupted while blocked waiting to re-acquire
 */
/**
 * 下面兩個是用于追蹤 呼叫 awaitXXX 方法時執行緒有沒有被中斷過
 * 主要的區別是
 *      REINTERRUPT: 代表執行緒是在 signal 后被中斷的        (REINTERRUPT = re-interrupt 再次中斷 最后會呼叫 selfInterrupt)
 *      THROW_IE: 代表在接受 signal 前被中斷的, 則直接拋出例外 (Throw_IE = throw inner exception)
 */
/** Mode meaning to reinterrupt on exit from wait */
/** 在離開 awaitXX方法, 退出前再次 自我中斷 (呼叫 selfInterrupt)*/
private static final int REINTERRUPT = 1;
/** Mode meaning to throw InterruptedException on exit from wait */
/**  在離開 awaitXX方法, 退出前再次, 以為在 接受 signal 前被中斷, 所以需要拋出例外 */
private static final int THROW_IE = -1;

11. Condition 中斷處理方法

該方法主要是查 在 awaitXX 方法中的這次喚醒是否是中斷引起的, 若是中斷引起的, 就進行 Node 的轉移

/**
 * Checks for interrupt, returning THROW_IE if interrupted
 * before signalled, REINTERRUPT if after signalled, or
 * 0 if not interrupted
 */
/**
 * 檢查 在 awaitXX 方法中的這次喚醒是否是中斷引起的
 * 若是中斷引起的, 則將 Node 從 Condition Queue 轉移到 Sync Queue 里面
 * 回傳值的區別:
 *      0: 此次喚醒是通過 signal -> LockSupport.unpark
 *      THROW_IE: 此次的喚醒是通過 interrupt, 并且 在 接受 signal 之前
 *      REINTERRUPT: 執行緒的喚醒是 接受過 signal 而后又被中斷
 */
private int checkInterruptWhileWaiting(Node node){
    return Thread.interrupted() ?
            (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) : 0;
}

12. Condition 中斷處理方法 reportInterruptAfterWait

/**
 * Throws InterruptedException, reinterrupts current thread, or
 * does nothing, depending on mode
 */
/**
 * 這個方法是在 awaitXX 方法離開前呼叫的, 主要是根據
 * interrupMode 判斷是拋出例外, 還是自我再中斷一下
 */
private void reportInterruptAfterWait(int interrupMode) throws InterruptedException{
    if(interrupMode == THROW_IE){
        throw new InterruptedException();
    }
    else if(interrupMode == REINTERRUPT){
        selfInterrupt();
    }
}

13. Condition 釋放鎖 進行等待的方法 await

await 此方法回應中斷請求, 當接受到中斷請求后會將節點從 Condition Queue 轉移到 Sync Queue

/**
 * Implements interruptible condition wait
 *
 * <li>
 *     If current thread is interrupted, throw InterruptedException
 *     Save lock state returned by {@link #getState()}
 *     Invoke {@link #release(int)} with saved state as argument,
 *     throwing IllegalMonitorStateException if it fails
 *     Blocking until signalled or interrupted
 *     Reacquire by invoking specifized version of
 *     {@link #acquire(int)} with saved state as argument.
 *     If interrupted while blocked in step 4, throw InterruptedException
 * </li>
 *
 * @throws InterruptedException
 */
/**
 * 支持 InterruptedException 的 await <- 注意這里即使是執行緒被中斷,
 * 還是需要獲取了獨占的鎖后, 再 呼叫 lock.unlock 進行釋放鎖
 */
@Override
public final void await() throws InterruptedException {
    if(Thread.interrupted()){                       // 1. 判斷執行緒是否中斷
        throw new InterruptedException();
    }
    Node node = addConditionWaiter();               // 2. 將執行緒封裝成一個 Node 放到 Condition Queue 里面, 其中可能有些清理作業
    int savedState = fullyRelease(node);           // 3. 釋放當前執行緒所獲取的所有的鎖 (PS: 呼叫 await 方法時, 當前執行緒是必須已經獲取了獨占的鎖)
    int interruptMode = 0;
    while(!isOnSyncQueue(node)){                  // 4. 判斷當前執行緒是否在 Sync Queue 里面(這里 Node 從 Condtion Queue 里面轉移到 Sync Queue 里面有兩種可能 (1) 其他執行緒呼叫 signal 進行轉移 (2) 當前執行緒被中斷而進行Node的轉移(就在checkInterruptWhileWaiting里面進行轉移))
        LockSupport.park(this);                   // 5. 當前執行緒沒在 Sync Queue 里面, 則進行 block
        if((interruptMode = checkInterruptWhileWaiting(node)) != 0){    // 6. 判斷此次執行緒的喚醒是否因為執行緒被中斷, 若是被中斷, 則會在checkInterruptWhileWaiting的transferAfterCancelledWait 進行節點的轉移; 回傳值 interruptMode != 0
            break;                                                      // 說明此是通過執行緒中斷的方式進行喚醒, 并且已經進行了 node 的轉移, 轉移到 Sync Queue 里面
        }
    }
    if(acquireQueued(node, savedState) && interruptMode != THROW_IE){ // 7. 呼叫 acquireQueued在 Sync Queue 里面進行 獨占鎖的獲取, 回傳值表明在獲取的程序中有沒有被中斷過
        interruptMode = REINTERRUPT;
    }
    if(node.nextWaiter != null){ // clean up if cancelled       // 8. 通過 "node.nextWaiter != null" 判斷 執行緒的喚醒是中斷還是 signal, 因為通過中斷喚醒的話, 此刻代表執行緒的 Node 在 Condition Queue 與 Sync Queue 里面都會存在
        unlinkCancelledWaiters();                                  // 9. 進行 cancelled 節點的清除
    }
    if(interruptMode != 0){                                     // 10. "interruptMode != 0" 代表通過中斷的方式喚醒執行緒
        reportInterruptAfterWait(interruptMode);                // 11. 根據 interruptMode 的型別決定是拋出例外, 還是自己再中斷一下
    }
}

14. Condition 釋放鎖 進行等待的方法 awaitNanos

awaitNanos 具有超時功能, 與回應中斷的功能, 不管中斷還是超時都會 將節點從 Condition Queue 轉移到 Sync Queue

/**
 * Impelemnts timed condition wait
 *
 * <li>
 *     If current thread is interrupted, throw InterruptedException
 *     Save lock state returned by {@link #getState()}
 *     Invoke {@link #release(int)} with saved state as argument,
 *     throwing IllegalMonitorStateException if it fails
 *     Block until aignalled, interrupted, or timed out
 *     Reacquire by invoking specified version of
 *     {@link #acquire(int)} with saved state as argument
 *     If interrupted while blocked in step 4, throw InterruptedException
 * </li>
 */
/**
 * 所有 awaitXX 方法其實就是
 *  0. 將當前的執行緒封裝成 Node 加入到 Condition 里面
 *  1. 丟到當前執行緒所擁有的 獨占鎖,
 *  2. 等待 其他獲取 獨占鎖的執行緒的喚醒, 喚醒從 Condition Queue 到 Sync Queue 里面, 進而獲取 獨占鎖
 *  3. 最后獲取 lock 之后, 在根據執行緒喚醒的方式(signal/interrupt) 進行處理
 *  4. 最后還是需要呼叫 lock./unlock 進行釋放鎖
 */
@Override
public final long awaitNanos(long nanosTimeout) throws InterruptedException {
    if(Thread.interrupted()){                                   // 1. 判斷執行緒是否中斷
        throw new InterruptedException();
    }
    Node node = addConditionWaiter();                           // 2. 將執行緒封裝成一個 Node 放到 Condition Queue 里面, 其中可能有些清理作業
    int savedState = fullyRelease(node);                       // 3. 釋放當前執行緒所獲取的所有的鎖 (PS: 呼叫 await 方法時, 當前執行緒是必須已經獲取了獨占的鎖)
    final long deadline = System.nanoTime() + nanosTimeout;   // 4. 計算 wait 的截止時間
    int interruptMode = 0;
    while(!isOnSyncQueue(node)){                              // 5. 判斷當前執行緒是否在 Sync Queue 里面(這里 Node 從 Condtion Queue 里面轉移到 Sync Queue 里面有兩種可能 (1) 其他執行緒呼叫 signal 進行轉移 (2) 當前執行緒被中斷而進行Node的轉移(就在checkInterruptWhileWaiting里面進行轉移))
        if(nanosTimeout <= 0L){                               // 6. 等待時間超時(這里的 nanosTimeout 是有可能 < 0),
            transferAfterCancelledWait(node);                 //  7. 呼叫 transferAfterCancelledWait 將 Node 從 Condition 轉移到 Sync Queue 里面
            break;
        }
        if(nanosTimeout >= spinForTimeoutThreshold){      // 8. 當剩余時間 < spinForTimeoutThreshold, 其實函式 spin 比用 LockSupport.parkNanos 更高效
            LockSupport.parkNanos(this, nanosTimeout);       // 9. 進行執行緒的 block
        }
        if((interruptMode = checkInterruptWhileWaiting(node)) != 0){   // 10. 判斷此次執行緒的喚醒是否因為執行緒被中斷, 若是被中斷, 則會在checkInterruptWhileWaiting的transferAfterCancelledWait 進行節點的轉移; 回傳值 interruptMode != 0
            break;                                                     // 說明此是通過執行緒中斷的方式進行喚醒, 并且已經進行了 node 的轉移, 轉移到 Sync Queue 里面
        }
        nanosTimeout = deadline - System.nanoTime();                    // 11. 計算剩余時間
    }

    if(acquireQueued(node, savedState) && interruptMode != THROW_IE){ // 12. 呼叫 acquireQueued在 Sync Queue 里面進行 獨占鎖的獲取, 回傳值表明在獲取的程序中有沒有被中斷過
        interruptMode = REINTERRUPT;
    }
    if(node.nextWaiter != null){                                    // 13. 通過 "node.nextWaiter != null" 判斷 執行緒的喚醒是中斷還是 signal, 因為通過中斷喚醒的話, 此刻代表執行緒的 Node 在 Condition Queue 與 Sync Queue 里面都會存在
        unlinkCancelledWaiters();                                      // 14. 進行 cancelled 節點的清除
    }
    if(interruptMode != 0){                                           // 15. "interruptMode != 0" 代表通過中斷的方式喚醒執行緒
        reportInterruptAfterWait(interruptMode);                      // 16. 根據 interruptMode 的型別決定是拋出例外, 還是自己再中斷一下
    }
    return deadline - System.nanoTime();                            // 17 這個回傳值代表是 通過 signal 還是 超時
}

15. Condition 釋放鎖 進行等待的方法 awaitUntil

/**
 * Implements absolute timed condition wait
 * <li>
 *     If current thread is interrupted, throw InterruptedException
 *     Save lock state returned by {@link #getState()}
 *     Invoke {@link #release(int)} with saved state as argument,
 *     throwing IllegalMonitorStateException if it fails
 *     Block until signalled, interrupted, or timed out
 *     Reacquire by invoking specialized version of
 *     {@link #acquire(int)} with saved state as argument
 *     if interrupted while blocked in step 4, throw InterruptedException
 *     If timeed out while blocked in step 4, return false, else true
 * </li>
 */
/**
 * 所有 awaitXX 方法其實就是
 *  0. 將當前的執行緒封裝成 Node 加入到 Condition 里面
 *  1. 丟到當前執行緒所擁有的 獨占鎖,
 *  2. 等待 其他獲取 獨占鎖的執行緒的喚醒, 喚醒從 Condition Queue 到 Sync Queue 里面, 進而獲取 獨占鎖
 *  3. 最后獲取 lock 之后, 在根據執行緒喚醒的方式(signal/interrupt) 進行處理
 *  4. 最后還是需要呼叫 lock./unlock 進行釋放鎖
 *
 *  awaitUntil 和 awaitNanos 差不多
 */
@Override
public boolean awaitUntil(Date deadline) throws InterruptedException {
    long abstime = deadline.getTime();                                      // 1. 判斷執行緒是否中斷
    if(Thread.interrupted()){
        throw new InterruptedException();
    }
    Node node = addConditionWaiter();                                       // 2. 將執行緒封裝成一個 Node 放到 Condition Queue 里面, 其中可能有些清理作業
    int savedState = fullyRelease(node);                                   // 3. 釋放當前執行緒所獲取的所有的鎖 (PS: 呼叫 await 方法時, 當前執行緒是必須已經獲取了獨占的鎖)
    boolean timeout = false;
    int interruptMode = 0;
    while(!isOnSyncQueue(node)){                                           // 4. 判斷當前執行緒是否在 Sync Queue 里面(這里 Node 從 Condtion Queue 里面轉移到 Sync Queue 里面有兩種可能 (1) 其他執行緒呼叫 signal 進行轉移 (2) 當前執行緒被中斷而進行Node的轉移(就在checkInterruptWhileWaiting里面進行轉移))
        if(System.currentTimeMillis() > abstime){                          // 5. 計算是否超時
            timeout = transferAfterCancelledWait(node);                    //  6. 呼叫 transferAfterCancelledWait 將 Node 從 Condition 轉移到 Sync Queue 里面
            break;
        }
        LockSupport.parkUntil(this, abstime);                              // 7. 進行 執行緒的阻塞
        if((interruptMode = checkInterruptWhileWaiting(node)) != 0){       // 8. 判斷此次執行緒的喚醒是否因為執行緒被中斷, 若是被中斷, 則會在checkInterruptWhileWaiting的transferAfterCancelledWait 進行節點的轉移; 回傳值 interruptMode != 0
            break;                                                         // 說明此是通過執行緒中斷的方式進行喚醒, 并且已經進行了 node 的轉移, 轉移到 Sync Queue 里面
        }
    }

    if(acquireQueued(node, savedState) && interruptMode != THROW_IE){   // 9. 呼叫 acquireQueued在 Sync Queue 里面進行 獨占鎖的獲取, 回傳值表明在獲取的程序中有沒有被中斷過
        interruptMode = REINTERRUPT;
    }
    if(node.nextWaiter != null){                                       // 10. 通過 "node.nextWaiter != null" 判斷 執行緒的喚醒是中斷還是 signal, 因為通過中斷喚醒的話, 此刻代表執行緒的 Node 在 Condition Queue 與 Sync Queue 里面都會存在
        unlinkCancelledWaiters();                                         // 11. 進行 cancelled 節點的清除
    }
    if(interruptMode != 0){                                             // 12. "interruptMode != 0" 代表通過中斷的方式喚醒執行緒
        reportInterruptAfterWait(interruptMode);                        // 13. 根據 interruptMode 的型別決定是拋出例外, 還是自己再中斷一下
    }

    return !timeout;                                                   // 13. 回傳是否通過 interrupt 進行執行緒的喚醒
}

16. Condition 的 instrumentation 方法

/**
 * Returns true if this condition was created by the given
 * synchronization object
 */
/**判斷當前 condition 是否獲取 獨占鎖 */
final boolean isOwnedBy(KAbstractOwnableSynchronizer sync){
    return sync == KAbstractQueuedSynchronizer.this;
}

/**
 * Quires whether any threads are waiting on this condition
 * Implements {@link KAbstractOwnableSynchronizer#"hasWaiters(ConditionObject)}
 *
 * @return {@code true} if there are any waiting threads
 * @throws IllegalMonitorStateException if {@link #isHeldExclusively()}
 *          returns {@code false}
 */
/**
 * 查看 Condition Queue 里面是否有 CONDITION 的節點
 */
protected final boolean hasWaiters(){
    if(!isHeldExclusively()){
        throw new IllegalMonitorStateException();
    }
    for(Node w = firstWaiter; w != null; w = w.nextWaiter ){
        if(w.waitStatus == Node.CONDITION){
            return true;
        }
    }
    return false;
}

/**
 * Returns an estimate of the number of threads waiting on
 * this condition
 * Implements {@link KAbstractOwnableSynchronizer#"getWaitQueueLength()}
 *
 * @return the estimated number of waiting threads
 * @throws IllegalMonitorStateException if {@link #isHeldExclusively()}
 *          return {@code false}
 */
/**
 * 獲取 Condition queue 里面的  CONDITION 的節點的個數
 */
protected final int getWaitQueueLength(){
    if(!isHeldExclusively()){
        throw new IllegalMonitorStateException();
    }
    int n = 0;
    for(Node w = firstWaiter; w != null; w = w.nextWaiter){
        if(w.waitStatus == Node.CONDITION){
            ++n;
        }
    }
    return n;
}

/**
 * Returns a collection containing those threads that may be
 * waiting on this Condition
 * Implements {@link KAbstractOwnableSynchronizer#'getWaitingThreads}
 *
 * @return the collection of thread
 * @throws IllegalMonitorStateException if {@link #isHeldExclusively()}
 *          returns {@code false}
 */
/**
 * 獲取 等待的執行緒
 */
protected final Collection<Thread> getWaitingThreads(){
    if(!isHeldExclusively()){
        throw new IllegalMonitorStateException();
    }
    ArrayList<Thread> list = new ArrayList<>();
    for(Node w = firstWaiter; w != null; w = w.nextWaiter){
        if(w.waitStatus == Node.CONDITION){
            Thread t = w.thread;
            if(t != null){
                list.add(t);
            }
        }
    }
    return list;
}

到此這篇關于文章就結束了!

點關注,不迷路!如果本文對你有幫助的話不要忘記點贊支持哦!

上述面試題答案都整理成檔案筆記, 也還整理了一些面試資料&最新2020收集的一些大廠的面試真題(都整理成文檔,小部分截圖),有需要的可以 點擊進入暗號:csdn ,

希望對大家有所幫助,有用的話點贊給我支持!

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/50163.html

標籤:其他

上一篇:[TopOpt] 針對99行改進的88行拓撲優化程式完全注釋

下一篇:Jeecg Boot 2.3 里程碑版本發布,支持微服務和單體自由切換、提供新行編輯表格JVXETable

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 面試突擊第一季,第二季,第三季

    第一季必考 https://www.bilibili.com/video/BV1FE411y79Y?from=search&seid=15921726601957489746 第二季分布式 https://www.bilibili.com/video/BV13f4y127ee/?spm_id_fro ......

    uj5u.com 2020-09-10 05:35:24 more
  • 第三單元作業總結

    1.前言 這應該是本學期最后一次寫作業總結了吧。總體來說,對作業的節奏也差不多掌握了,作業做起來的效率也更高了。雖然和之前的作業一樣,作業中都要用到新的知識,但是相比之前,更加懂得了如何利用工具以及資料。雖然之間卡過殼,但總體而言,這幾次作業還算完成的比較好。 2.作業程序總結 相比前兩個單元,此單 ......

    uj5u.com 2020-09-10 05:35:41 more
  • 北航OO(2020)第四單元博客作業暨課程總結博客

    北航OO(2020)第四單元博客作業暨課程總結博客 本單元作業的架構設計 在本單元中,由于UML圖具有比較清晰的樹形結構,因此我對其中需要進行查詢操作的元素進行了包裝,在樹的父節點中存盤所有孩子的參考。考慮到性能問題,我采用了快取機制,一次查詢后盡可能快取已經遍歷過的資訊,以減少遍歷次數。 本單元我 ......

    uj5u.com 2020-09-10 05:35:48 more
  • BUAA_OO_第四單元

    一、UML決議器設計 ? 先看下題目:第四單元實作一個基于JDK 8帶有效性檢查的UML(Unified Modeling Language)類圖,順序圖,狀態圖分析器 MyUmlInteraction,實際上我們要建立一個有向圖模型,UML中的物件(元素)可能與同級元素連接,也可與低級元素相連形成 ......

    uj5u.com 2020-09-10 05:35:54 more
  • 6.1邏輯運算子

    邏輯運算子 1. && 短路與 運算式1 && 運算式2 01.運算式1為true并且運算式2也為true 整體回傳為true 02.運算式1為false,將不會執行運算式2 整體回傳為false 03.只要有一個運算式為false 整體回傳為false 2. || 短路或 運算式1 || 運算式2 ......

    uj5u.com 2020-09-10 05:35:56 more
  • BUAAOO 第四單元 & 課程總結

    1. 第四單元:StarUml檔案決議 本單元采用了圖模型決議UML。 UML檔案可以抽象為圖、子圖、邊的邏輯結構。 在實作中,圖的節點包括類、介面、屬性,子圖包括狀態圖、順序圖等。 采用了三次遍歷UML元素的方法建圖,第一遍遍歷建點,第二、三次遍歷設定屬性、連邊,實作圖物件的初始化。這里借鑒了一些 ......

    uj5u.com 2020-09-10 05:36:06 more
  • 談談我對C# 多型的理解

    面向物件三要素:封裝、繼承、多型。 封裝和繼承,這兩個比較好理解,但要理解多型的話,可就稍微有點難度了。今天,我們就來講講多型的理解。 我們應該經常會看到面試題目:請談談對多型的理解。 其實呢,多型非常簡單,就一句話:呼叫同一種方法產生了不同的結果。 具體實作方式有三種。 一、多載 多載很簡單。 p ......

    uj5u.com 2020-09-10 05:36:09 more
  • Python 資料驅動工具:DDT

    背景 python 的unittest 沒有自帶資料驅動功能。 所以如果使用unittest,同時又想使用資料驅動,那么就可以使用DDT來完成。 DDT是 “Data-Driven Tests”的縮寫。 資料:http://ddt.readthedocs.io/en/latest/ 使用方法 dd. ......

    uj5u.com 2020-09-10 05:36:13 more
  • Python里面的xlrd模塊詳解

    那我就一下面積個問題對xlrd模塊進行學習一下: 1.什么是xlrd模塊? 2.為什么使用xlrd模塊? 3.怎樣使用xlrd模塊? 1.什么是xlrd模塊? ?python操作excel主要用到xlrd和xlwt這兩個庫,即xlrd是讀excel,xlwt是寫excel的庫。 今天就先來說一下xl ......

    uj5u.com 2020-09-10 05:36:28 more
  • 當我們創建HashMap時,底層到底做了什么?

    jdk1.7中的底層實作程序(底層基于陣列+鏈表) 在我們new HashMap()時,底層創建了默認長度為16的一維陣列Entry[ ] table。當我們呼叫map.put(key1,value1)方法向HashMap里添加資料的時候: 首先,呼叫key1所在類的hashCode()計算key1 ......

    uj5u.com 2020-09-10 05:36:38 more
最新发布
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:20:47 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:20:25 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:20:17 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:20:10 more
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:19:44 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:19:07 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:18:57 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:18:49 more
  • 05單件模式

    #經典的單件模式 public class Singleton { private static Singleton uniqueInstance; //一個靜態變數持有Singleton類的唯一實體。 // 其他有用的實體變數寫在這里 //構造器宣告為私有,只有Singleton可以實體化這個類! ......

    uj5u.com 2023-04-19 08:42:51 more
  • 【架構與設計】常見微服務分層架構的區別和落地實踐

    軟體工程的方方面面都遵循一個最基本的道理:沒有銀彈,架構分層模型更是如此,每一種都有各自優缺點,所以請根據不同的業務場景,并遵循簡單、可演進這兩個重要的架構原則選擇合適的架構分層模型即可。 ......

    uj5u.com 2023-04-19 08:42:41 more