-
CountDownLatch
-
功能
1.需要等待多個線程都完成某個任務,然後喚醒主線程繼續執行;
2.當兩個線程時,主線程需要等待子線程完成任務到某個進度,然後喚醒主線程繼續執行。 -
使用
1.創建CountDownLatch對象,給一個初始值n,可以理解爲進度數字n
2.執行CountDownLatch的await方法,主線程會被阻塞,直到CountDownLatch設置的n等於0
3.在你認爲需要調用CountDownLatch的countDown方法地方調用,這個時候其實n就會減一
4.如果你調用了countDown方法n次,那麼主線程會從阻塞狀態中被喚醒,主線程繼續執行 -
原理
1.基於AQS實現的,利用AQS的同步功能
2.CountDownLatch的await方法其實就是將當前線程加入到阻塞隊列,然後掛起當前線程
3.CountDownLatch的countDown方法其實就是嘗試喚醒阻塞隊列中的線程,當n=0的時候,主線程對應在阻 塞隊 列的節點會被喚醒(n>0時會喚醒失敗,但是每次都會嘗試喚醒),喚醒後,主線程就繼續執行。 -
代碼
public void await() throws InterruptedException { sync.acquireSharedInterruptibly(1); } private void doAcquireSharedInterruptibly(int arg) throws InterruptedException { //加入到阻塞隊列 final Node node = addWaiter(Node.SHARED); boolean failed = true; try { for (;;) { final Node p = node.predecessor(); if (p == head) { //status值大於0,那麼r會返回-1 int r = tryAcquireShared(arg); if (r >= 0) { setHeadAndPropagate(node, r); p.next = null; // help GC failed = false; return; } } //正常情況喚醒失敗,線程掛起 if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) throw new InterruptedException(); } } finally { if (failed) cancelAcquire(node); } }
public void countDown() { sync.releaseShared(1); } public final boolean releaseShared(int arg) { //tryReleaseShared方法只有在status==0的情況下才會返回true,否則只對status-1, //然後返回false if (tryReleaseShared(arg)) { //doReleaseShared喚醒阻塞隊列的線程,也就是頭結點的next doReleaseShared(); return true; } return false; } private void doReleaseShared() { for (;;) { Node h = head; if (h != null && h != tail) { int ws = h.waitStatus; if (ws == Node.SIGNAL) { if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) continue; // loop to recheck cases //喚醒阻塞隊列第一個線程(頭結點不屬於阻塞隊列元素) unparkSuccessor(h); } else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) continue; // loop on failed CAS } if (h == head) // loop if head changed break; } }
-
-
CyclicBarrier
-
功能
1.需要等待多個子線程都完成某個任務,最後一個子線程或者主線程喚醒所有子線程;
2.喚醒子線程們後,所有線程繼續執行,或者也可以指定一個任務,在突破阻塞點的時候執行
3.可以重複使用,一次阻塞點被解除後可以繼續使用 -
使用
1.創建CyclicBarrier對象,給一個初始值n,也就是多少線程在阻塞點後繼續執行
2.在你的程序中調用CyclicBarrier的await方法,表示到達阻塞點,這個時候當前線程阻塞,這個方法既用於同步阻塞使用,也用於解除阻塞使用,當調用await是最後一個阻塞點的時候,會解除所謂的柵欄,這一點也是和CountDownLatch最大的區別,在實現方式上。 -
原理
1.基於AQS實現的,利用AQS的同步功能
2.CyclicBarrier的await方法會將當前線程加入到等待隊列,然後掛起當前線程,等到最後一個await方法調用的時候,會喚醒(將條件隊列轉移到阻塞隊列)所有條件隊列的節點,最後喚醒阻塞隊列的第一個節點開始執行。 -
代碼
private int dowait(boolean timed, long nanos) throws InterruptedException, BrokenBarrierException, TimeoutException { final ReentrantLock lock = this.lock; lock.lock(); try { final Generation g = generation; if (g.broken) throw new BrokenBarrierException(); if (Thread.interrupted()) { breakBarrier(); throw new InterruptedException(); } //每次調用await方法都會將count減一 int index = --count; //如果是最後一個await被調用,那麼執行下面的邏輯 if (index == 0) { // tripped boolean ranAction = false; try { //執行CyclicBarrier創建時指定的任務 final Runnable command = barrierCommand; if (command != null) command.run(); ranAction = true; //將所有等待隊列的節點加入到阻塞隊列中,重新初始化,可以重複使用目的 nextGeneration(); return 0; } finally { if (!ranAction) breakBarrier(); } } // loop until tripped, broken, interrupted, or timed out //如果不是最後一個await,那麼count>0,執行下面邏輯 for (;;) { try { if (!timed) //將當前線程加入到等待隊列中,當前線程掛起 trip.await(); else if (nanos > 0L) nanos = trip.awaitNanos(nanos); } catch (InterruptedException ie) { if (g == generation && ! g.broken) { breakBarrier(); throw ie; } else { // We're about to finish waiting even if we had not // been interrupted, so this interrupt is deemed to // "belong" to subsequent execution. Thread.currentThread().interrupt(); } } if (g.broken) throw new BrokenBarrierException(); if (g != generation) return index; if (timed && nanos <= 0L) { breakBarrier(); throw new TimeoutException(); } } } finally { //喚醒阻塞隊列,讓阻塞隊列中任務開始執行 lock.unlock(); } }
-
-
總結
使用上:- CountDownLatch定義的阻塞點,也就是那個n,並不代表多少個線程,只是代表countDown的執行次數
- CyclicBarrier定義的阻塞點,也就是那個n,必須是要在n個線程中調用await方法,因爲await會阻塞當前線程,所以調用多次也是徒勞,必須要n個線程調用才能消耗掉這個n
原理上
- CountDownLatch是直接基於阻塞隊列的,一個CountDownLatch就是阻塞隊列的一個節點,和n沒關係
- CyclicBarrier是基於條件隊列的,n是多少就有多少個節點體現在等待隊列中,和n有關係。
(十二)JDK源碼分析之常用併發工具類
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.