快來看看!AQS 和 CountDownLatch 有怎麼樣的關係?

{"type":"doc","content":[{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"前言"}]},{"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":"CountDownLatch 一個同步輔助工具,同樣是基於 AQS 實現,本篇文件主要是介紹 CountDownLatch 的使用,以及源碼。"}]},{"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":"公衆號:liuzhihangs,記錄工作學習中的技術、開發及源碼筆記;時不時分享一些生活中的見聞感悟。歡迎大佬來指導!"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"介紹"}]},{"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":"一個同步輔助工具,允許一個或多個線程等待,直到在其他線程中執行的一組操作完成爲止"}]},{"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":"一個 CountDownLatch 初始化爲給定計數。 在 await 方法阻塞,調用 countDown 方法會減少計數直到達到零,此後所有等待的線程被釋放,任何後續調用 await 都會立即返回。 這是一次性的現象 - 計數不能復位。 如果你需要一個版本重置計數,請考慮使用CyclicBarrier 。"}]},{"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":"CountDownLatch 是一種通用的同步工具,可用於多種用途。 "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":"1","normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"用作一個簡單的開/關鎖存器,或者門:所有線程調用await在門口等待,直到被調用 countDown 的線程打開。 "}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"初始化計數爲 N ,用一個線程等待,直到 N 個線程完成某項操作,或某些動作已經完成 N 次。"}]}]}]},{"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":"CountDownLatch 一個有用的屬性是,它不要求調用 countDown 線程等待計數到達零之前繼續,它只是阻止任何線程通過await ,直到所有線程可以通過。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"基本使用"}]},{"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":"在我之前 CAS 那篇文章《"},{"type":"link","attrs":{"href":"https://mp.weixin.qq.com/s/nSZW-bZ_snQ0ZYyzeEBJiw","title":""},"content":[{"type":"text","text":"從JUC源碼看CAS,我做了個筆記 ......"}]},{"type":"text","text":"》中介紹 CAS 舉例時使用了 CountDownLatch,其代碼如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public class CasTest {\n\n private static final CountDownLatch LATCH = new CountDownLatch(10);\n\n private static int NUM_I = 0;\n private static volatile int NUM_J = 0;\n private static final AtomicInteger NUM_K = new AtomicInteger(0);\n\n public static void main(String[] args) throws InterruptedException {\n\n ExecutorService threadPool = Executors.newFixedThreadPool(10);\n for (int i = 0; i < 10; i++) {\n\n threadPool.execute(new Runnable() {\n public void run() {\n for (int j = 0; j < 10000; j++) {\n NUM_I++;\n NUM_J++;\n NUM_K.incrementAndGet();\n }\n LATCH.countDown();\n }\n });\n }\n LATCH.await();\n\n System.out.println(\"NUM_I = \" + NUM_I);\n System.out.println(\"NUM_J = \" + NUM_J);\n System.out.println(\"NUM_K = \" + NUM_K.get());\n threadPool.shutdown();\n }\n\n}"}]},{"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":"簡單介紹下這段代碼的主要邏輯及功能:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":"1","normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"CountDownLatch 初始化計數爲 10 。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"開 10 個線程去處理業務邏輯,業務邏輯結束會調用 LATCH.countDown() 對計數進行 -1 操作。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"在 LATCH.await() 處會阻塞等待,直到 LATCH 的值爲 0 ,即 10 個線程業務都處理結束。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"然後主線程繼續執行。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"問題疑問"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":"1","normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"CountDownLatch 和 AQS 有什麼關係?"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"CountDownLatch 的實現原理是什麼?"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"源碼分析"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"基本結構"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/49/49d697970c435d29fc7c8f84b392dcad.png","alt":"uml-VAGlMb","title":null,"style":null,"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":"通過類圖可以看出,CountDownLatch 內部存在一個靜態類 Sync,而 Sync 繼承了 AbstractQueuedSynchronizer。具體內部是如何實現的,則下面通過源碼和畫圖一步一步的進行介紹。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"初始化"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public CountDownLatch(int count) {\n if (count < 0) throw new IllegalArgumentException(\"count < 0\");\n this.sync = new Sync(count);\n}"}]},{"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":"通過初始化構造器可以看出,在 new 創建對象時必須傳遞一個 int 類型的非負數。實現邏輯可以看出,是創建了一個 Sync 對象。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"private static final class Sync extends AbstractQueuedSynchronizer {\n private static final long serialVersionUID = 4982264981922014374L;\n\n Sync(int count) {\n setState(count);\n }\n\n int getCount() {\n return getState();\n }\n\n} "}]},{"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 源碼中已經介紹了 state 的含義,state 在不同子類中代表不同的含義。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":"1","normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"在 ReentrantLock 中 state 代表加鎖狀態,0 沒有線程獲得鎖,大於等於 1 已經有線程獲得鎖,大於 1 說明該獲得鎖的線程多次重入。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"在 ReentrantReadWriteLock 中 state 代表鎖的狀態。state 爲 0 ,沒有線程持有鎖,state 的高 16 爲代表讀鎖狀態,低 16 爲代表寫鎖狀態。通過位運算可以獲取讀寫鎖的實際值。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"而在這裏 (CountDownLatch)則代表門閂或者說計數的值。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"countDown"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public void countDown() {\n sync.releaseShared(1);\n}"}]},{"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":"遞減鎖存器的計數:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果當前計數大於零,則遞減。 "}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果計數到達零,則釋放所有等待的線程。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果那麼當前計數等於零沒有任何反應。"}]}]}]},{"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 的 releaseShard() 方法,釋放共享資源。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"// AQS 代碼\npublic final boolean releaseShared(int arg) {\n if (tryReleaseShared(arg)) {\n doReleaseShared();\n return true;\n }\n return false;\n}\n"}]},{"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 釋放共享資源方法中 "},{"type":"text","marks":[{"type":"strong"}],"text":"tryReleaseShared(arg)"},{"type":"text","text":" 部分是在 CountDownLatch 的內部類 Sync 中實現的,代碼部分如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"protected boolean tryReleaseShared(int releases) {\n // Decrement count; signal when transition to zero\n for (;;) {\n int c = getState();\n if (c == 0)\n return false;\n int nextc = c-1;\n if (compareAndSetState(c, nextc))\n return nextc == 0;\n }\n}"}]},{"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":"遞減更新 state ,如果 state 爲 0 則返回 false,否則返回 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":"此時再對照上面 AQS 代碼,發現:如果 tryReleaseShared 返回 true ,則會喚醒後續節點開始執行操作。所以也就是說,如果 state 不爲 0,則不會喚醒後續節點,直到 state 爲 0 。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"await"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public void await() throws InterruptedException {\n sync.acquireSharedInterruptibly(1);\n}"}]},{"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":"導致當前線程等待,直到鎖存器倒計數至零,除非線程被中斷。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果當前計數爲零,則此方法立即返回。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果當前計數大於零,則當前線程用於線程調度目的,禁用並一直處於休眠狀態的兩件事情之一發生:"}]}]}]},{"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":" - 因調用countDown方法使計數達到0; "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" - 其他某些線程中斷當前線程。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public final void acquireSharedInterruptibly(int arg)\n throws InterruptedException {\n if (Thread.interrupted())\n throw new InterruptedException();\n if (tryAcquireShared(arg) < 0)\n doAcquireSharedInterruptibly(arg);\n}"}]},{"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 定義了 tryAcquireShared 返回值分爲 3 種:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":"1","normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"小於 0: 表示失敗;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"等於 0: 表示共享模式獲取資源成功,但後續的節點不能以共享模式獲取成功; "}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"大於 0: 表示共享模式獲取資源成功,後續節點在共享模式獲取也可能會成功,在這種情況下,後續等待線程必須檢查可用性。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"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":"其中 tryAcquireShared 同樣由 CountDownLatch 的內部類 Sync 中實現,內部邏輯主要是判斷 state 的值,進行返回。"}]},{"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":"在內部實現中返回的值只有 1 和 -1 ,說明在 state == 0 時,返回 1 ,即喚醒後續節點。不等於 0 時,會阻塞。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"protected int tryAcquireShared(int acquires) {\n return (getState() == 0) ? 1 : -1;\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"總結"}]},{"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","marks":[{"type":"strong"}],"text":"Q: CountDownLatch 和 AQS 有什麼關係?"}]},{"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","marks":[{"type":"strong"}],"text":"A:"},{"type":"text","text":" CountDownLatch 是基於 AQS 的共享模式實現的。"}]},{"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","marks":[{"type":"strong"}],"text":"Q: CountDownLatch 的實現原理是什麼?"}]},{"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","marks":[{"type":"strong"}],"text":"A:"},{"type":"text","text":" 可以參考上面的源碼解析,進行總結介紹。 CountDownLatch 是基於 AQS 共享模式實現的,在初始化時必須傳入計數,該計數實際上是 AQS 的 state 值。在 countDown 時對 state 進行遞減,在 當 state 爲 0 時 會喚醒 AQS 隊列中的所有等待的節點 (因爲是共享模式)。而 await 方法是判斷 state 的值,如果不是 0 ,則所有線程在隊列中阻塞,等待喚醒。"}]},{"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","marks":[{"type":"strong"}],"text":"Q: state 在代表的含義是什麼?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"A:"},{"type":"text","text":" "}]},{"type":"numberedlist","attrs":{"start":"1","normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"在 ReentrantLock 中 state 代表加鎖狀態,0 沒有線程獲得鎖,大於等於 1 已經有線程獲得鎖,大於 1 說明該獲得鎖的線程多次重入。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"在 ReentrantReadWriteLock 中 state 代表鎖的狀態。state 爲 0 ,沒有線程持有鎖,state 的高 16 爲代表讀鎖狀態,低 16 爲代表寫鎖狀態。通過位運算可以獲取讀寫鎖的實際值。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"而在這裏 (CountDownLatch)則代表門閂或者說計數的值。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章