深入理解Condition實現原理

在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在同步隊列上的節點排隊排到了隊首。

  1. public final void await() throws InterruptedException {
  2. //如果當前線程中斷,拋出異常
  3. if (Thread.interrupted())
  4. throw new InterruptedException();
  5. //1.將當前線程封裝成一個node並加入到等待隊列隊尾(這裏如果lastWaiter是CANCELLED取消狀態,那麼會把它踢出Condition隊列)。
  6. Node node = addConditionWaiter();
  7. //2.釋放當前線程的獨佔鎖,不管重入幾次,都把state釋放爲0
  8. int savedState = fullyRelease(node);
  9. int interruptMode = 0;
  10. //3.判斷當前節點沒有在同步隊列上,沒有在同步隊列上(即還沒有被signal),則將當前線程阻塞
  11. while (!isOnSyncQueue(node)) {
  12. LockSupport.park(this);
  13. //4.判斷標記兩種中斷:是在被signal前中斷還是在被signal後中斷,分別標記上THROW_IE和REINTERRUPT。
  14. if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
  15. break;
  16. }
  17. //5.這個時候線程已經被signal()或者signalAll()操作給喚醒了,退出了3中的while循環自旋等待,嘗試再次獲取鎖
  18. if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
  19. interruptMode = REINTERRUPT;
  20. if (node.nextWaiter != null) // clean up if cancelled 清除等待隊列中等待狀態不爲CONDITION的節點
  21. unlinkCancelledWaiters();
  22. //6.在第4步中標記的中斷狀態,如果是在被signal前中斷還是在被signal後中斷,如果是被signal前就被中斷則拋出 InterruptedException,否則執行 Thread.currentThread().interrupt();
  23. if (interruptMode != 0)
  24. reportInterruptAfterWait(interruptMode);
  25. }

addConditionWaiter源碼

  1. //addConditionWaiter()方法主要是將線程封裝成節點,添加到等待隊列尾部
  2. private Node addConditionWaiter() {
  3. Node t = lastWaiter;
  4. //Condition裏面的節點狀態不是等待狀態CONDITION時,會清除節點
  5. if (t != null && t.waitStatus != Node.CONDITION) {
  6. unlinkCancelledWaiters();
  7. t = lastWaiter;
  8. }
  9. //將當前線程分裝成一個node,加到等待多了尾部
  10. Node node = new Node(Thread.currentThread(), Node.CONDITION);
  11. if (t == null)
  12. firstWaiter = node;
  13. else
  14. t.nextWaiter = node;
  15. lastWaiter = node;
  16. return node;
  17. }
  18. //從等待隊列頭部開始循環清除等待狀態waitStatus不爲CONDITION的節點
  19. private void unlinkCancelledWaiters() {
  20. Node t = firstWaiter;
  21. Node trail = null;
  22. while (t != null) {
  23. Node next = t.nextWaiter;
  24. if (t.waitStatus != Node.CONDITION) {
  25. t.nextWaiter = null;
  26. if (trail == null)
  27. firstWaiter = next;
  28. else
  29. trail.nextWaiter = next;
  30. if (next == null)
  31. lastWaiter = trail;
  32. }
  33. else
  34. trail = t;
  35. t = next;
  36. }
  37. }

fullyRelease()源碼

fullyRelease()方法中會調用release()釋放掉state鎖,不管重入幾次,都把state釋放爲0,同時喚醒同步隊列的後繼節點。

  1. final int fullyRelease(Node node) {
  2. boolean failed = true;
  3. try {
  4. //獲取持有state鎖的次數
  5. int savedState = getState();
  6. //把state釋放爲0
  7. if (release(savedState)) {
  8. failed = false;
  9. return savedState;
  10. } else {
  11. throw new IllegalMonitorStateException();
  12. }
  13. } finally {
  14. //釋放鎖失敗,再將node設置爲CANCELLED狀態
  15. if (failed)
  16. node.waitStatus = Node.CANCELLED;
  17. }
  18. }

isOnSyncQueue()源碼

isOnSyncQueue()方法主要是判斷node是不是在同步隊列中,只有在同步隊列中的線程纔會被阻塞。

  1. final boolean isOnSyncQueue(Node node) {
  2. //如果當前節點狀態是CONDITION或node.prev是null,則證明當前節點在等待隊列上而不是同步隊列上。用node.prev來判斷,是因爲一個節點如果要加入同步隊列,在加入前就會設置好prev字段。
  3. if (node.waitStatus == Node.CONDITION || node.prev == null)
  4. return false;
  5. //如果node.next不爲null,則一定在同步隊列上,因爲node.next是在節點加入同步隊列後設置的
  6. if (node.next != null) // If has successor, it must be on queue
  7. return true;
  8. //從等待隊列的尾部遍歷判斷node是否在等待隊列中
  9. return findNodeFromTail(node);
  10. }
  11. private boolean findNodeFromTail(Node node) {
  12. Node t = tail;
  13. for (;;) {
  14. if (t == node)
  15. return true;
  16. if (t == null)
  17. return false;
  18. t = t.prev;
  19. }
  20. }

reportInterruptAfterWait()方法源碼

reportInterruptAfterWait()方法會根據中斷狀態來判斷是拋出異常,還是執行中斷。即判斷線程是在被signal前中斷,還是在被signal後中斷;如果是被signal前就被中斷則拋出 InterruptedException,否則執行 Thread.currentThread().interrupt()。

  1. private void reportInterruptAfterWait(int interruptMode)
  2. throws InterruptedException {
  3. //如果是在調signal()前就被中斷則拋出異常
  4. if (interruptMode == THROW_IE)
  5. throw new InterruptedException();
  6. //如果是在調signal()方法後中斷,就執行中斷
  7. else if (interruptMode == REINTERRUPT)
  8. selfInterrupt();
  9. }

到此condition的wait()方法分析就完了,可以看出,await()的操作過程和Object.wait()方法是一樣,只不過await()採用了Condition等待隊列的方式實現了Object.wait()的功能。

(2)通知的實現

調用Condition的signal()方法,將會喚醒在等待隊列中等待時間最長的節點(首節點),在喚醒節點之前,會將等待隊列中節點移到同步隊列中。Condition的signal()方法將節點從等待隊列移動到同步隊列邏輯如下圖:

整個signal()的過程可以總結如下:

  • (1)執行signal()喚醒線程時,先判斷當前線程是否是同步鎖state持有線程,所以能夠調用signal()方法的線程一定持有了同步鎖state。

  • (2)自旋喚醒等待隊列的firstWaiter(首節點),在喚醒firstWaiter節點之前,會將等待隊列首節點移到同步隊列中。

signal()源碼

  1. public final void signal() {
  2. //判斷當前線程是否是state鎖持有線程
  3. if (!isHeldExclusively())
  4. throw new IllegalMonitorStateException();
  5. Node first = firstWaiter;
  6. //等待隊列首節點不爲null時,喚醒首節點
  7. if (first != null)
  8. doSignal(first);
  9. }
  10. //自旋喚醒首節點
  11. private void doSignal(Node first) {
  12. do {
  13. //移動頭節點指針firstWaiter
  14. if ( (firstWaiter = first.nextWaiter) == null)
  15. lastWaiter = null;
  16. //從等待隊列中移除首節點
  17. first.nextWaiter = null;
  18. } while (!transferForSignal(first) //transferForSignal方法嘗試喚醒當前節點,如果喚醒失敗,則繼續嘗試喚醒
  19. && (first = firstWaiter) != null);
  20. }
  21. //嘗試喚醒當前節點,並將當前節點移動到同步隊列中
  22. final boolean transferForSignal(Node node) {
  23. //如果當前節點狀態爲CONDITION,則將狀態改爲0準備加入同步隊列;如果當前狀態不爲CONDITION,說明該節點等待已被中斷
  24. if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
  25. return false;
  26. //將node添加到同步隊列中,返回的p是node節點在同步隊列中的先驅節點
  27. Node p = enq(node);
  28. int ws = p.waitStatus;
  29. //如果先驅節點的狀態爲CANCELLED(大於0) 或設置先驅節點的狀態爲SIGNAL失敗,那麼就立即喚醒當前節點對應的線程,線程被喚醒後會執行await()方法中的acquireQueued()方法,該方法會重新嘗試將節點的先驅狀態設爲SIGNAL並再次park線程;如果當前設置前驅節點狀態爲SIGNAL成功,那麼就不需要馬上喚醒線程了,當它的前驅節點成爲同步隊列的首節點且釋放同步狀態後,會自動喚醒它。
  30. if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
  31. LockSupport.unpark(node.thread);
  32. return true;
  33. }

signalAll()源碼

signalAll()會從首節點循環遍歷等待隊列,將等待隊列中的所有節點移到同步隊列中去。

  1. public final void signalAll() {
  2. //判斷當前線程是否是state鎖持有線程
  3. if (!isHeldExclusively())
  4. throw new IllegalMonitorStateException();
  5. Node first = firstWaiter;
  6. if (first != null)
  7. doSignalAll(first);
  8. }
  9. //遍歷等待隊列,將等待隊列中的node移動到同步隊裏中
  10. private void doSignalAll(Node first) {
  11. lastWaiter = firstWaiter = null;
  12. do {
  13. Node next = first.nextWaiter;
  14. first.nextWaiter = null;
  15. //移動節點到同步隊裏中
  16. transferForSignal(first);
  17. first = next;
  18. } while (first != null);
  19. }

(3)總結

  • (1)Condition等待通知的本質就是等待隊列 和 同步隊列的交互的過程,跟object的wait()/notify()機制一樣;Condition是基於同步鎖state實現的,而objec是基於monitor模式實現的。

  • (2)一個lock(AQS)可以有多個Condition,即多個等待隊列,只有一個同步隊列。

  • (3)Condition.await()方法執行時,會將同步隊列裏的head鎖釋放掉,把線程封裝成新node添加到等待隊列中;Condition.signal()方法執行時,會把等待隊列中的首節點移到同步隊列中去,直到鎖state被獲取才被喚醒。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章