1.背景
閱讀該源碼的前提是,已經閱讀了reentrantLock的源碼!
2.await源碼解讀
condition代碼理解的核心,其實就是理解到:
線程節點如何從sync雙向鏈表隊列到指定的條件隊列中,
然後又如何從條件隊列中到sync雙向鏈表隊列的
一定要先把下面的2個圖理解到,再去看源碼和斷點調試就很容易理解了
核心邏輯圖:
核心代碼邏輯圖:
2.1.await方法詳解
代碼解讀:
/** * 進入條件等待 */ public final void await() throws InterruptedException { // 是否有中斷標記 if (Thread.interrupted()) throw new InterruptedException(); // 將線程加入到條件等待隊列 Node node = addConditionWaiter(); /* fullyRelease(node) 釋放鎖並喚醒後繼節點 這裏要結合ReentrantLock來理解,執行到這裏說明是獲取到了鎖的, 這裏就是要釋放ReentrantLock鎖,然後進入到條件隊列中等待*/ int savedState = fullyRelease(node); // interruptMode =0表示沒有中斷, interruptMode =1表示退出時重新中斷 ,interruptMode=-1表示退出時拋異常 int interruptMode = 0; while (!isOnSyncQueue(node)) { // 在條件隊列中等待 LockSupport.park(this); // checkInterruptWhileWaiting(node)返回0,表示沒有中斷 if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) break; } // acquireQueued(node, savedState) 從sync隊列中重新獲取鎖,並處理中斷標記 if (acquireQueued(node, savedState) && interruptMode != THROW_IE) interruptMode = REINTERRUPT; // node結點不是最後一個結點,清除條件隊列中無效的節點 if (node.nextWaiter != null) // clean up if cancelled unlinkCancelledWaiters(); // 重新處理中斷,具體中斷方式取決於 interruptMode 的值, // interruptMode =1表示退出時重新中斷 ,interruptMode=-1表示退出時拋異常int interruptMode = 0; if (interruptMode != 0) reportInterruptAfterWait(interruptMode); }
2.2.addConditionWaiter方法詳解
代碼解讀:
/** * 添加新的節點到條件隊列 * 這裏的條件隊列是 單鏈表,不是雙鏈表 * CONDITION = -2 表示是條件隊列 */ private Node addConditionWaiter() { Node t = lastWaiter; // t.waitStatus != Node.CONDITION ?? 這個條件的作用 if (t != null && t.waitStatus != Node.CONDITION) { // 去掉取消節點 unlinkCancelledWaiters(); // 將取消的節點,去掉後,尾節點可能會變 t = lastWaiter; } Node node = new Node(Thread.currentThread(), Node.CONDITION); if (t == null) // 第一次進入條件隊列 firstWaiter = node; else // 將當前節點放在尾節點之後 t.nextWaiter = node; // 設置新的尾節點 lastWaiter = node; // 返回當前節點 return node; }
2.3.fullyRelease方法詳解
代碼解讀:
/** * 釋放鎖,並喚醒後繼節點 * * @param node * @return */ final int fullyRelease(Node node) { boolean failed = true; try { // 獲取當前資源狀態 int savedState = getState(); // release(savedState) 釋放鎖並喚醒後繼節點 if (release(savedState)) { failed = false; return savedState; } else { throw new IllegalMonitorStateException(); } } finally { // 釋放鎖失敗節點取消 if (failed) node.waitStatus = Node.CANCELLED; } }
2.4.isOnSyncQueue方法詳解
代碼解讀:
/** * 判定節點是否在sync隊列中 * * @param node * @return */ final boolean isOnSyncQueue(Node node) { // 如果節點標記位是CONDITION = -2的狀態 或者 沒有前繼節點,說明節點不在隊列中 if (node.waitStatus == Node.CONDITION || node.prev == null) return false; // 如果下一個節點不爲空說明節點在隊列裏面 if (node.next != null) // If has successor, it must be on queue return true; // 遍歷sync隊列,查看節點是否在隊列裏面 return findNodeFromTail(node); }
2.5.findNodeFromTail方法詳解
代碼解讀:
/** * 遍歷節點,查看傳入的節點是否在隊列裏面,在裏面返回true * * @param node * @return */ private boolean findNodeFromTail(Node node) { Node t = tail; for (; ; ) { if (t == node) return true; if (t == null) return false; // 從後往前遍歷,還記得之前我們在講ReentrantLock的源碼時說過爲什麼要從後往前遍歷麼? t = t.prev; } }
2.6.checkInterruptWhileWaiting方法詳解
代碼解讀:
/** * // 該模式意味着在退出等待時重新中斷 * private static final int REINTERRUPT = 1; * // 該模式意味着在退出等待時拋出InterruptedException * private static final int THROW_IE = -1; * * @param node * @return */ private int checkInterruptWhileWaiting(Node node) { // 當前線程沒有被中斷直接返回0 // 當前線程已經被中斷了的話 return Thread.interrupted() ? // 取消時重新入隊列成功,標記爲退出時拋出異常 // 取消時重新入隊列失敗,標記位退出時重新中斷 (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) : 0; }
2.7.transferAfterCancelledWait方法詳解
代碼解讀:
/** * 條件等待隊列中的節點如果已經被取消,將節點添加到sync隊列的尾部 * * @param node * @return 節點添加到尾部成功返回true, 否則返回false */ final boolean transferAfterCancelledWait(Node node) { // 初始化節點 if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) { // 將節點添加到尾部 enq(node); // 添加到隊列成功返回true return true; } // 判斷節點是否在Sync隊列裏面 while (!isOnSyncQueue(node)) // 不在隊列裏面則等待,直到線程執行完成 Thread.yield(); // 添加失敗,返回false return false; }
2.8.unlinkCancelledWaiters方法詳解
代碼解讀:
/** * 作用:刪除單項向鏈表中已經取消的節點,即狀態不等於2的節點 * 這是典型單向鏈表刪除節點的邏輯,如果對這個段代碼不是很理解, * 可以查看之前的數據結果部分 */ private void unlinkCancelledWaiters() { Node t = firstWaiter; Node trail = null; while (t != null) { Node next = t.nextWaiter; if (t.waitStatus != Node.CONDITION) { // 斷開連接,幫助GC回收 t.nextWaiter = null; if (trail == null) // 重新定義頭結點 firstWaiter = next; else trail.nextWaiter = next; if (next == null) // 最後的有效尾節點 lastWaiter = trail; } else { trail = t; } // 指針後移 t = next; } }
2.9.reportInterruptAfterWait方法詳解
代碼解讀:
/** * 中斷的具體處理方式 * interruptMode =1表示退出時重新中斷 ,interruptMode=-1表示退出時拋異常 * * @param interruptMode * @throws InterruptedException */ private void reportInterruptAfterWait(int interruptMode) throws InterruptedException { if (interruptMode == THROW_IE) // 拋出異常的處理方式 throw new InterruptedException(); else if (interruptMode == REINTERRUPT) // 自我中斷的處理方式 selfInterrupt(); }
2.10.selfInterrupt方法詳解
代碼解讀:
/** * 當前線程執行中斷處理 */ static void selfInterrupt() { Thread.currentThread().interrupt(); }
3.signal源碼解讀
3.1.signal源碼解讀
代碼:
/** * 喚醒條件隊列中的節點 */ public final void signal() { // 檢查當前線程是否是擁有鎖的線程 if (!isHeldExclusively()) throw new IllegalMonitorStateException(); Node first = firstWaiter; if (first != null) // 執行喚醒方法 doSignal(first); }
3.2.isHeldExclusively源碼解讀
代碼:
/** * 判定當前線程是否是擁有鎖的線程 * @return */ protected final boolean isHeldExclusively() { return getExclusiveOwnerThread() == Thread.currentThread(); }
3.3.doSignal源碼解讀
代碼:
/** * 執行喚醒條件隊列中的節點 * @param first */ private void doSignal(Node first) { do { // 這個if的判定就是檢查條件隊列中是否還有節點,如果沒有了,就將lastWaiter設置爲null if ( (firstWaiter = first.nextWaiter) == null) lastWaiter = null; // 第一個節點出隊列後,斷開引用 first.nextWaiter = null; } while (!transferForSignal(first) && (first = firstWaiter) != null); }
3.4.transferForSignal源碼解讀
/** * 喚醒條件隊列中的節點--> 到 sync對列中去 * @param node * @return */ final boolean transferForSignal(Node node) { // 修改狀態爲 sync隊列的初始化狀態 if (!compareAndSetWaitStatus(node, Node.CONDITION, 0)) return false; // 將節點加入到隊列尾部 Node p = enq(node); int ws = p.waitStatus; if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL)) // 換醒節點 LockSupport.unpark(node.thread); return true; }
4.測試
不論你是否理解了源碼,都建議大家多使用斷點調試查看
節點是如何進入隊列,
如何出隊列,
如何掛起線程,
如何喚醒線程的.....
測試代碼
package com.my.aqs.condition; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class ConditionTest { // 功能標記位 private int flag = 1; // 創建非公平鎖 private Lock lock = new ReentrantLock(false); // 條件鎖1-燒水 private Condition condition1 = lock.newCondition(); // 條件鎖2-泡茶 private Condition condition2 = lock.newCondition(); // 條件鎖3-喝茶 private Condition condition3 = lock.newCondition(); /** * 功能:燒水 */ public void method01() { String threadName = Thread.currentThread().getName(); try { lock.lock(); while (flag != 1) { System.out.println(" "+threadName + ",需要,掛起當前線程,進入條件等待隊列,因爲當前不是燒水標記1,而是:" + flag); condition1.await(); } System.out.println(threadName + ":正在燒水..."); // System.out.println(threadName + ":燒水完成,喚醒泡茶線程"); flag = 2; condition2.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } /** * 功能:泡茶 */ public void method02() { String threadName = Thread.currentThread().getName(); try { lock.lock(); while (flag != 2) { System.out.println(" "+threadName + ",需要,掛起當前線程,進入條件等待隊列,因爲當前不是泡茶標記2,而是:" + flag); condition2.await(); } System.out.println(threadName + ":正在泡茶..."); // System.out.println(threadName + ":泡茶完成,喚醒喝茶線程"); flag = 3; condition3.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } /** * 功能:喝茶 */ public void method03() { String threadName = Thread.currentThread().getName(); try { lock.lock(); while (flag != 3) { System.out.println(" "+threadName + ",需要,掛起當前線程,進入條件等待隊列,因爲當前不是喝茶標記3,而是:" + flag); condition3.await(); } System.out.println(threadName + ":正在喝茶..."); // System.out.println(threadName + ":喝茶完成,喚醒燒水線程"); flag = 1; condition1.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } /** * 睡眠時間是爲了讓線程按照這個順序進入隊列等待 喝茶->泡茶->燒水 * @param args */ public static void main(String[] args) { ConditionTest conditionTest = new ConditionTest(); for (int i = 1; i <= 2; i++) { // 燒水 new Thread(() -> { try { Thread.sleep(5*1000L); } catch (InterruptedException e) { e.printStackTrace(); } conditionTest.method01(); }, "燒水-線程 " + i).start(); // 泡茶 new Thread(() -> { try { Thread.sleep(5*100L); } catch (InterruptedException e) { e.printStackTrace(); } conditionTest.method02(); }, "泡茶-線程 " + i).start(); // 喝茶 new Thread(() -> { try { Thread.sleep(100L); } catch (InterruptedException e) { e.printStackTrace(); } conditionTest.method03(); }, "喝茶-線程 " + i).start(); } } }
斷點調試圖:
測試結果: