AQS介紹和原理分析(下)-條件中斷

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"接着上篇文章沒說完的條件中斷,我們先來看一個例子:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"//首先創建一個可重入鎖,它本質是獨佔鎖\nprivate final ReentrantLock takeLock = new ReentrantLock();\n//創建該鎖上的條件隊列\nprivate final Condition notEmpty = takeLock.newCondition();\n//使用過程\npublic E take() throws InterruptedException {\n //首先進行加鎖\n takeLock.lockInterruptibly();\n try {\n //如果隊列是空的,則進行等待\n notEmpty.await();\n //取元素的操作...\n \n //如果有剩餘,則喚醒等待元素的線程\n notEmpty.signal();\n } finally {\n //釋放鎖\n takeLock.unlock();\n }\n //取完元素以後喚醒等待放入元素的線程\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上面的代碼片段截取自LinkedBlockingQueue,是Java常用的阻塞隊列之一。注意一點:條件隊列是建立在鎖基礎上的,而且必須是獨佔鎖","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"分析","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"等待條件的過程:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"在操作條件隊列之前首先需要成功獲取獨佔鎖。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"成功獲取獨佔鎖以後,如果當前條件還不滿足,則在當前鎖的條件隊列上掛起,與此同時釋放掉當前獲取的鎖資源(如果不釋放鎖資源會發生什麼?)","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"如果被喚醒,則檢查是否可以獲取獨佔鎖,否則繼續掛起。","attrs":{}}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"條件滿足後的喚醒過程(以喚醒一個節點爲例,也可以喚醒多個):","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"把當前等待隊列中的第一個有效節點(如果被取消就無效了)加入同步隊列等待被前置節點喚醒,如果此時前置節點被取消,則直接喚醒該節點讓它重新在同步隊列裏適當的嘗試獲取鎖或者掛起。","attrs":{}}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這裏要注意,整個AQS分爲兩個隊列,","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"一個同步隊列,一個條件隊列","attrs":{}},{"type":"text","text":",如圖","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/af/af113423c641a3ffb4223c66254ece25.jpeg","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"源碼分析","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"執行過程:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/83/83332292131f5e677e3f541e42657832.jpeg","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"以我們上面提到的例子來分析","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"//條件隊列入口,參考上面的代碼片段\npublic final void await() throws InterruptedException {\n //如果當前線程被中斷則直接拋出異常\n if (Thread.interrupted())\n throw new InterruptedException();\n //把當前節點加入條件隊列\n Node node = addConditionWaiter();\n //釋放掉已經獲取的獨佔鎖資源\n int savedState = fullyRelease(node);\n int interruptMode = 0;\n //如果不在同步隊列中則不斷掛起\n while (!isOnSyncQueue(node)) {\n LockSupport.park(this);\n //中斷處理,另一種跳出循環的方式\n if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)\n break;\n }\n //走到這裏說明節點已經條件滿足被加入到了同步隊列中或者中斷了\n //這個方法很熟悉吧?就跟獨佔鎖調用同樣的獲取鎖方法,從這裏可以看出條件隊列只能用於獨佔鎖\n if (acquireQueued(node, savedState) && interruptMode != THROW_IE)\n interruptMode = REINTERRUPT;\n //走到這裏說明已經成功獲取到了獨佔鎖,接下來就做些收尾工作\n //刪除條件隊列中被取消的節點\n if (node.nextWaiter != null) \n unlinkCancelledWaiters();\n //根據不同模式處理中斷\n if (interruptMode != 0)\n reportInterruptAfterWait(interruptMode);\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"首先看下加入條件隊列的代碼","attrs":{}}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"/1.與同步隊列不同,條件隊列頭尾指針是firstWaiter跟lastWaiter\n//2.條件隊列是在獲取鎖之後,也就是臨界區進行操作,因此很多地方不用考慮併發\nprivate Node addConditionWaiter() {\n Node t = lastWaiter;\n //如果最後一個節點被取消,則刪除隊列中被取消的節點\n if (t != null && t.waitStatus != Node.CONDITION) {\n\n unlinkCancelledWaiters();\n t = lastWaiter;\n }\n //創建一個類型爲CONDITION的節點並加入隊列,由於在臨界區,所以這裏不用併發控制\n Node node = new Node(Thread.currentThread(), Node.CONDITION);\n if (t == null)\n firstWaiter = node;\n else\n t.nextWaiter = node;\n lastWaiter = node;\n return node;\n}\n\n//刪除取消節點的邏輯雖然長,但比較簡單就是鏈表刪除\nprivate void unlinkCancelledWaiters() {\n Node t = firstWaiter;\n Node trail = null;\n while (t != null) {\n Node next = t.nextWaiter;\n if (t.waitStatus != Node.CONDITION) {\n t.nextWaiter = null;\n if (trail == null)\n firstWaiter = next;\n else\n trail.nextWaiter = next;\n if (next == null)\n lastWaiter = trail;\n }\n else\n trail = t;\n t = next;\n }\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":2,"normalizeStart":2},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"把節點加入到條件隊列中以後,接下來要做的就是釋放鎖資源","attrs":{}}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"//入參就是新創建的節點,即當前節點\nfinal int fullyRelease(Node node) {\n boolean failed = true;\n try {\n //這裏這個取值要注意,獲取當前的state並釋放,這從另一個角度說明必須是獨佔鎖(可以考慮下這個邏輯放在共享鎖下面會發生什麼?)\n int savedState = getState();\n //跟獨佔鎖釋放鎖資源一樣,不贅述\n if (release(savedState)) {\n failed = false;\n return savedState;\n } else {\n //如果這裏釋放失敗,則拋出異常\n throw new IllegalMonitorStateException();\n }\n } finally {\n //如果釋放鎖失敗,則把節點取消,由這裏就能看出來上面添加節點的邏輯中只需要判斷最後一個節點是否被取消就可以了\n if (failed)\n node.waitStatus = Node.CANCELLED;\n }\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":3,"normalizeStart":3},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"接下來就該掛起了(先忽略中斷處理,單看掛起邏輯)","attrs":{}}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"//如果不在同步隊列就繼續掛起(signal操作會把節點加入同步隊列)\n while (!isOnSyncQueue(node)) {\n LockSupport.park(this);\n //中斷處理後面再分析\n if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)\n break;\n }\n//判斷節點是否在同步隊列中\nfinal boolean isOnSyncQueue(Node node) {\n //快速判斷1:節點狀態或者節點沒有前置節點\n //注:同步隊列是有頭節點的,而條件隊列沒有\n if (node.waitStatus == Node.CONDITION || node.prev == null)\n return false;\n //快速判斷2:next字段只有同步隊列纔會使用,條件隊列中使用的是nextWaiter字段\n if (node.next != null) \n return true;\n //上面如果無法判斷則進入複雜判斷\n return findNodeFromTail(node);\n}\n\n//注意這裏用的是tail,這是因爲條件隊列中的節點是被加入到同步隊列尾部,這樣查找更快\n//從同步隊列尾節點開始向前查找當前節點,如果找到則說明在,否則不在\nprivate boolean findNodeFromTail(Node node) {\n Node t = tail;\n for (;;) {\n if (t == node)\n return true;\n if (t == null)\n return false;\n t = t.prev;\n }\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":4,"normalizeStart":4},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"如果被喚醒且已經被轉移到了同步隊列,則會執行與獨佔鎖一樣的方法acquireQueued()進行同步隊列獨佔獲取.最後我們來梳理一下里面的中斷邏輯以及收尾工作的代碼","attrs":{}}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":" while (!isOnSyncQueue(node)) {\n LockSupport.park(this);\n //這裏被喚醒可能是正常的signal操作也可能是中斷\n if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)\n break;\n }\n //這裏的判斷邏輯是:\n //1.如果現在不是中斷的,即正常被signal喚醒則返回0\n //2.如果節點由中斷加入同步隊列則返回THROW_IE,由signal加入同步隊列則返回REINTERRUPT\n private int checkInterruptWhileWaiting(Node node) {\n return Thread.interrupted() ?\n (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :\n 0;\n }\n\n //修改節點狀態並加入同步隊列\n //該方法返回true表示節點由中斷加入同步隊列,返回false表示由signal加入同步隊列\n final boolean transferAfterCancelledWait(Node node) {\n //這裏設置節點狀態爲0,如果成功則加入同步隊列\n if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {\n //與獨佔鎖同樣的加入隊列邏輯,不贅述\n enq(node);\n return true;\n }\n //如果上面設置失敗,說明節點已經被signal喚醒,由於signal操作會將節點加入同步隊列,我們只需自旋等待即可\n while (!isOnSyncQueue(node))\n Thread.yield();\n return false;\n }\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":5,"normalizeStart":5},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":5,"align":null,"origin":null},"content":[{"type":"text","text":"在把喚醒後的中斷判斷做好以後,看await()中最後一段邏輯","attrs":{}}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"//在處理中斷之前首先要做的是從同步隊列中成功獲取鎖資源\nif (acquireQueued(node, savedState) && interruptMode != THROW_IE)\n interruptMode = REINTERRUPT;\n//由於當前節點可能是由於中斷修改了節點狀態,所以如果有後繼節點則執行刪除已取消節點的操作\n//如果沒有後繼節點,根據上面的分析在後繼節點加入的時候會進行刪除\nif (node.nextWaiter != null) \n unlinkCancelledWaiters();\nif (interruptMode != 0)\n reportInterruptAfterWait(interruptMode);\n\n//根據中斷時機選擇拋出異常或者設置線程中斷狀態\nprivate void reportInterruptAfterWait(int interruptMode) throws InterruptedException {\n if (interruptMode == THROW_IE)\n throw new InterruptedException();\n else if (interruptMode == REINTERRUPT)\n //實現代碼爲:Thread.currentThread().interrupt();\n selfInterrupt();\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":6,"normalizeStart":6},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":6,"align":null,"origin":null},"content":[{"type":"text","text":"最後signal()方法相對容易一些,一起看源碼分析下","attrs":{}}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"//條件隊列喚醒入口\npublic final void signal() {\n //如果不是獨佔鎖則拋出異常,再次說明條件隊列只適用於獨佔鎖\n if (!isHeldExclusively())\n throw new IllegalMonitorStateException();\n //如果條件隊列不爲空,則進行喚醒操作\n Node first = firstWaiter;\n if (first != null)\n doSignal(first);\n}\n\n//該方法就是把一個有效節點從條件隊列中刪除並加入同步隊列\n//如果失敗則會查找條件隊列上等待的下一個節點直到隊列爲空\nprivate void doSignal(Node first) {\n do {\n if ( (firstWaiter = first.nextWaiter) == null)\n lastWaiter = null;\n first.nextWaiter = null;\n } while (!transferForSignal(first) &&(first = firstWaiter) != null);\n}\n\n//將節點加入同步隊列\nfinal boolean transferForSignal(Node node) {\n //修改節點狀態,這裏如果修改失敗只有一種可能就是該節點被取消,具體看上面await過程分析\n if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))\n return false;\n //該方法很熟悉了,跟獨佔鎖入隊方法一樣,不贅述\n Node p = enq(node);\n //注:這裏的p節點是當前節點的前置節點\n int ws = p.waitStatus;\n //如果前置節點被取消或者修改狀態失敗則直接喚醒當前節點\n //此時當前節點已經處於同步隊列中,喚醒會進行鎖獲取或者正確的掛起操作\n if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))\n LockSupport.unpark(node.thread);\n return true;\n}\n","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"小總結","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"至此,AQS的關鍵特性在這裏都分析完畢了,這裏做個總結。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們首先關注獨佔鎖和共享鎖的實現,其次關注它公平/非公平,超時,條件中斷的特性。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"整個AQS依靠一個state狀態,兩個隊列(CLH和ConditionObject)來實現,隊列中都是Node節點,節點中的狀態:0,SINAL,CONDITION,- - --- CANCEL都在分析中出現過了,相信以後知道這些都是在什麼時候用的了。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"AQS在許多設計思路都值得借鑑,比如典型的自旋等待、CLH隊列。","attrs":{}}]}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"參考","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"https://www.cnblogs.com/onlywujun/articles/3531568.htmlhttp://ifeve.com/introduce-abstractqueuedsynchronizer/https://www.cnblogs.com/lfls/p/7615982.html?utm_source=debugrun&utm_medium=referral","attrs":{}}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章