在AQS中存在兩個FIFO隊列:同步隊列 和 等待隊列。本篇文章主要是講condition實現原理(即等待隊裏),同步隊列實現原理看這篇文章:深入理解AQS實現原理和源碼分析。等待隊列是由Condition內部實現的,是一個虛擬的FIFO單向隊列,在AQS中同步隊列、等待隊列組成關係如下圖:
-
(1)AQS中tail 和 head主要構成了一個FIFO雙向的同步隊列。
-
(2)AQS中condition構成了一個FIFO單向等待隊列。condition是AQS內部類,每個Condition對象中保存了firstWaiter和lastWaiter作爲隊列首節點和尾節點,每個節點使用Node.nextWaiter保存下一個節點的引用,因此等待隊列是一個單向隊列。
1.隊列關係
在Object的監視器(monitor)模型上,一個對象擁有一個同步隊列和一個等待隊列;而併發包中的AQS上擁有一個同步隊列和多個等待隊列。兩者的具體實現原理的有所不同,但在多線程下等待/喚醒 操作的思路有相同之處,Object的監視器模型 和 AQS對同步隊列、等待隊列對應關係如下圖
(1)Object的監視器模型同步、等待隊列對應關係圖
多個線程併發訪問某個對象監視器(Monitor對象)的時候,即多線程執行Synchonized處的代碼時,monitor處理過程包括:
-
(1)thread進入Synchonized代碼時,會執行Monitor.Enter命令來獲取monitor對象。如果命令執行成功獲取Monitor對象成功,執行失敗線程會進入synchronized同步隊列中,線程處於BLOCKED,直到monitor對象被釋放。
-
(2)thread執行完Synchonized同步代碼塊後,會執行Monitor.exit命令來釋放monitor對象,並通知同步隊列會獲取monitor對象。
-
(3)如果線程執行object.wait(),線程會進入synchronized等待隊列進行WAITING,直到其他線程線程執行notify()或notifyAll()方法,將等待隊列中的一個或多個等待線程從等待隊列中移到同步隊列中,被移動的線程狀態由WAITING變爲BLOCKED。
(2)AQS中同步、等待隊列對應關係圖
當多線程併發訪問AQS的lock()、await()、single()方法時,同步隊列和等待隊列變化處理過程包括:
- (1)多個形成執行lock()方法時,線程會競爭獲取同步鎖state,獲取成功的線程佔有鎖state、獲取失敗的線程會封裝成node加入到AQS的同步隊列中,等待鎖state的釋放。
- (2)等獲取了state鎖的線程(同步隊列中head節點)執行await()方法時,condition會將當前線程封裝成一個新的node添加到condition等待隊列的尾部,同時阻塞(waiting),直到被喚醒。
- (3)等獲取了state鎖的線程(同步隊列中head節點)single()方法時,condition會將等待隊列首節點移動到同步隊列的尾部,直到獲取同步鎖state才被喚醒。
兩者的對比關係圖:
2.Condition的實現
(1)等待的實現
當線程調用Condition.await()方法時,將會把前線程封裝成node節點,並將節點加入等待隊列的尾部,然後釋放同步state狀態,喚醒同步隊列中的後繼節點,然後當前線程會進入等待狀態。當前線程加入Condition的等待隊列邏輯如下圖:
-
(1)能夠調用Condition.await()方法的節點是獲取了同步state鎖的node,即同步隊列中的head節點;調用Condition的await()方法(或者以await開頭的方法)會使當前線程進入等待隊列並釋放鎖、喚醒同步隊列中的後繼節點,最後線程狀態變爲等待狀態。
-
(2)Condition擁有首尾節點的引用,而新增節點只需要將原有的尾節點nextWaiter指向它,並且更新尾節點即可。
-
(3)調用Condition.await()節點引用更新的過程並沒有使用CAS保證,原因在於調用await()方法的線程必定是獲取了state鎖的線程,也就是說該過程是由鎖來保證線程安全的。
注意:同步隊列的首節點並不會直接加入等待隊列,而是把當前線程構封裝成一個新的節點並將其加入等待隊列中。
await()方法源碼
整個await()的執行的過程可以總結如下幾步:
-
(1)將當前線程封裝成node加入Condition等待隊列尾部。
-
(2)釋放state鎖:不管重入幾次,都把state釋放爲0,同時喚醒同步隊列的後繼節點。
-
(3)自旋:直到node節點在等待隊列上的節點移動到了同步隊列(通過其他線程調用signal())或被中斷。
-
(4)阻塞當前節點,直到node獲取到了鎖,也就是node在同步隊列上的節點排隊排到了隊首。
- public final void await() throws InterruptedException {
- //如果當前線程中斷,拋出異常
- if (Thread.interrupted())
- throw new InterruptedException();
- //1.將當前線程封裝成一個node並加入到等待隊列隊尾(這裏如果lastWaiter是CANCELLED取消狀態,那麼會把它踢出Condition隊列)。
- Node node = addConditionWaiter();
- //2.釋放當前線程的獨佔鎖,不管重入幾次,都把state釋放爲0
- int savedState = fullyRelease(node);
- int interruptMode = 0;
- //3.判斷當前節點沒有在同步隊列上,沒有在同步隊列上(即還沒有被signal),則將當前線程阻塞
- while (!isOnSyncQueue(node)) {
- LockSupport.park(this);
- //4.判斷標記兩種中斷:是在被signal前中斷還是在被signal後中斷,分別標記上THROW_IE和REINTERRUPT。
- if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
- break;
- }
- //5.這個時候線程已經被signal()或者signalAll()操作給喚醒了,退出了3中的while循環自旋等待,嘗試再次獲取鎖
- if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
- interruptMode = REINTERRUPT;
- if (node.nextWaiter != null) // clean up if cancelled 清除等待隊列中等待狀態不爲CONDITION的節點
- unlinkCancelledWaiters();
- //6.在第4步中標記的中斷狀態,如果是在被signal前中斷還是在被signal後中斷,如果是被signal前就被中斷則拋出 InterruptedException,否則執行 Thread.currentThread().interrupt();
- if (interruptMode != 0)
- reportInterruptAfterWait(interruptMode);
- }
addConditionWaiter源碼
- //addConditionWaiter()方法主要是將線程封裝成節點,添加到等待隊列尾部
- private Node addConditionWaiter() {
- Node t = lastWaiter;
- //Condition裏面的節點狀態不是等待狀態CONDITION時,會清除節點
- if (t != null && t.waitStatus != Node.CONDITION) {
- unlinkCancelledWaiters();
- t = lastWaiter;
- }
- //將當前線程分裝成一個node,加到等待多了尾部
- Node node = new Node(Thread.currentThread(), Node.CONDITION);
- if (t == null)
- firstWaiter = node;
- else
- t.nextWaiter = node;
- lastWaiter = node;
- return node;
- }
- //從等待隊列頭部開始循環清除等待狀態waitStatus不爲CONDITION的節點
- 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;
- }
- }
fullyRelease()源碼
fullyRelease()方法中會調用release()釋放掉state鎖,不管重入幾次,都把state釋放爲0,同時喚醒同步隊列的後繼節點。
- final int fullyRelease(Node node) {
- boolean failed = true;
- try {
- //獲取持有state鎖的次數
- int savedState = getState();
- //把state釋放爲0
- if (release(savedState)) {
- failed = false;
- return savedState;
- } else {
- throw new IllegalMonitorStateException();
- }
- } finally {
- //釋放鎖失敗,再將node設置爲CANCELLED狀態
- if (failed)
- node.waitStatus = Node.CANCELLED;
- }
- }
isOnSyncQueue()源碼
isOnSyncQueue()方法主要是判斷node是不是在同步隊列中,只有在同步隊列中的線程纔會被阻塞。
- final boolean isOnSyncQueue(Node node) {
- //如果當前節點狀態是CONDITION或node.prev是null,則證明當前節點在等待隊列上而不是同步隊列上。用node.prev來判斷,是因爲一個節點如果要加入同步隊列,在加入前就會設置好prev字段。
- if (node.waitStatus == Node.CONDITION || node.prev == null)
- return false;
- //如果node.next不爲null,則一定在同步隊列上,因爲node.next是在節點加入同步隊列後設置的
- if (node.next != null) // If has successor, it must be on queue
- return true;
- //從等待隊列的尾部遍歷判斷node是否在等待隊列中
- return findNodeFromTail(node);
- }
-
- private boolean findNodeFromTail(Node node) {
- Node t = tail;
- for (;;) {
- if (t == node)
- return true;
- if (t == null)
- return false;
- t = t.prev;
- }
- }
reportInterruptAfterWait()方法源碼
reportInterruptAfterWait()方法會根據中斷狀態來判斷是拋出異常,還是執行中斷。即判斷線程是在被signal前中斷,還是在被signal後中斷;如果是被signal前就被中斷則拋出 InterruptedException,否則執行 Thread.currentThread().interrupt()。
- private void reportInterruptAfterWait(int interruptMode)
- throws InterruptedException {
- //如果是在調signal()前就被中斷則拋出異常
- if (interruptMode == THROW_IE)
- throw new InterruptedException();
- //如果是在調signal()方法後中斷,就執行中斷
- else if (interruptMode == REINTERRUPT)
- selfInterrupt();
- }
到此condition的wait()方法分析就完了,可以看出,await()的操作過程和Object.wait()方法是一樣,只不過await()採用了Condition等待隊列的方式實現了Object.wait()的功能。
(2)通知的實現
調用Condition的signal()方法,將會喚醒在等待隊列中等待時間最長的節點(首節點),在喚醒節點之前,會將等待隊列中節點移到同步隊列中。Condition的signal()方法將節點從等待隊列移動到同步隊列邏輯如下圖:
整個signal()的過程可以總結如下:
-
(1)執行signal()喚醒線程時,先判斷當前線程是否是同步鎖state持有線程,所以能夠調用signal()方法的線程一定持有了同步鎖state。
-
(2)自旋喚醒等待隊列的firstWaiter(首節點),在喚醒firstWaiter節點之前,會將等待隊列首節點移到同步隊列中。
signal()源碼
- public final void signal() {
- //判斷當前線程是否是state鎖持有線程
- if (!isHeldExclusively())
- throw new IllegalMonitorStateException();
- Node first = firstWaiter;
- //等待隊列首節點不爲null時,喚醒首節點
- if (first != null)
- doSignal(first);
- }
- //自旋喚醒首節點
- private void doSignal(Node first) {
- do {
- //移動頭節點指針firstWaiter
- if ( (firstWaiter = first.nextWaiter) == null)
- lastWaiter = null;
- //從等待隊列中移除首節點
- first.nextWaiter = null;
- } while (!transferForSignal(first) //transferForSignal方法嘗試喚醒當前節點,如果喚醒失敗,則繼續嘗試喚醒
- && (first = firstWaiter) != null);
- }
-
- //嘗試喚醒當前節點,並將當前節點移動到同步隊列中
- final boolean transferForSignal(Node node) {
- //如果當前節點狀態爲CONDITION,則將狀態改爲0準備加入同步隊列;如果當前狀態不爲CONDITION,說明該節點等待已被中斷
- if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
- return false;
- //將node添加到同步隊列中,返回的p是node節點在同步隊列中的先驅節點
- Node p = enq(node);
- int ws = p.waitStatus;
- //如果先驅節點的狀態爲CANCELLED(大於0) 或設置先驅節點的狀態爲SIGNAL失敗,那麼就立即喚醒當前節點對應的線程,線程被喚醒後會執行await()方法中的acquireQueued()方法,該方法會重新嘗試將節點的先驅狀態設爲SIGNAL並再次park線程;如果當前設置前驅節點狀態爲SIGNAL成功,那麼就不需要馬上喚醒線程了,當它的前驅節點成爲同步隊列的首節點且釋放同步狀態後,會自動喚醒它。
- if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
- LockSupport.unpark(node.thread);
- return true;
- }
signalAll()源碼
signalAll()會從首節點循環遍歷等待隊列,將等待隊列中的所有節點移到同步隊列中去。
- public final void signalAll() {
- //判斷當前線程是否是state鎖持有線程
- if (!isHeldExclusively())
- throw new IllegalMonitorStateException();
- Node first = firstWaiter;
- if (first != null)
- doSignalAll(first);
- }
- //遍歷等待隊列,將等待隊列中的node移動到同步隊裏中
- private void doSignalAll(Node first) {
- lastWaiter = firstWaiter = null;
- do {
- Node next = first.nextWaiter;
- first.nextWaiter = null;
- //移動節點到同步隊裏中
- transferForSignal(first);
- first = next;
- } while (first != null);
- }
(3)總結
-
(1)Condition等待通知的本質就是等待隊列 和 同步隊列的交互的過程,跟object的wait()/notify()機制一樣;Condition是基於同步鎖state實現的,而objec是基於monitor模式實現的。
-
(2)一個lock(AQS)可以有多個Condition,即多個等待隊列,只有一個同步隊列。
-
(3)Condition.await()方法執行時,會將同步隊列裏的head鎖釋放掉,把線程封裝成新node添加到等待隊列中;Condition.signal()方法執行時,會把等待隊列中的首節點移到同步隊列中去,直到鎖state被獲取才被喚醒。