JUC(3)---CountDownLatch、CyclicBarrier和AQS

白露非霜發表於2020-05-16

CountDownLatch:

  可以讓一個執行緒等待其他執行緒完成了各自的工作之後再執行。比如說一個切菜,一個人切肉,都準備完畢之後才能炒肉。

構造方法

public CountDownLatch(int count)  count等待的執行緒數量

關鍵API

countDown()   分執行緒執行完減少計數

await()        主執行緒等待呼叫

使用

package com.nijunyang.concurrent;

import java.util.concurrent.CountDownLatch;

/**
 * Description:
 * Created by nijunyang on 2020/5/16 13:53
 */
public class CountDownLatchTest{

    private CountDownLatch countDownLatch;

    public CountDownLatchTest(CountDownLatch countDownLatch) {
        this.countDownLatch = countDownLatch;
    }

    public static void main(String[] args) throws InterruptedException {

        CountDownLatch countDownLatch = new CountDownLatch(2);
        CountDownLatchTest countDownLatchTest = new CountDownLatchTest(countDownLatch);

        new Thread(()-> {
            try {
                countDownLatchTest.method1();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"執行緒1").start();
        new Thread(()-> {
            try {
                countDownLatchTest.method2();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"執行緒2").start();

        System.out.println("等待食材準備完畢...");
        countDownLatch.await();
        System.out.println("炒肉...");


//        System.out.println("------第二次使用-----");
//        new Thread(()-> {
//            try {
//                countDownLatchTest.method1();
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
//        },"執行緒1").start();
//        new Thread(()-> {
//            try {
//                countDownLatchTest.method2();
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
//        },"執行緒2").start();
//
//        System.out.println("等待食材準備完畢...");
//        countDownLatch.await();
//        System.out.println("炒肉...");

    }

    private void method1() throws InterruptedException {
        Thread.sleep(5000L);
        System.out.println("切菜完畢...");
        countDownLatch.countDown();
    }

    private void method2() throws InterruptedException {
        Thread.sleep(10000L);
        System.out.println("切肉完畢...");
        countDownLatch.countDown();
    }
}

 

原理解析

1.從構造方法進去我們可以看到又是一個熟悉的Sync內部類繼承了AbstractQueuedSynchronizer,入參的數量被賦值到AbstractQueuedSynchronizer的state欄位。

2.await方法會去判斷state是否等於0,如果不等於0,說明其他執行緒還沒有執行完畢。就會執行doAcquireSharedInterruptibly這個方法,將當前這個呼叫await方法的執行緒入隊阻塞。

(呼叫鏈:await()-sync.acquireSharedInterruptibly-sync.tryAcquireShared-doAcquireSharedInterruptibly)

3.countDown方法,每調一次就會將state的值減1,當扣減到0的時候去喚醒上面等待的主執行緒執行(呼叫鏈:countDown-sync.releaseShared-sync.tryReleaseShared-doReleaseShared(減到0才會執行這方法))

 

 

 

 

 

CyclicBarrier

籬柵,顧名思義有攔截作用。它可以讓一組執行緒到達柵欄時被阻塞,直到最後一個執行緒到達,才放行通過。比如玩LOL,需要等待所有玩家進度條100%了,才能進入遊戲

構造方法

CyclicBarrier(int parties)      parties:阻塞的執行緒數量

CyclicBarrier(int parties, Runnable barrierAction)  parties:阻塞的執行緒數量  barrierAction:當最後一個執行緒到達是先執行這個任務,再去執行後面的流程。

關鍵API:

await()  到達柵欄點等待。呼叫次數要和入引數量一致,否則會一致阻塞的等待。

使用

package com.nijunyang.concurrent;

import java.util.concurrent.CyclicBarrier;

/**
 * @author: create by nijunyang
 * @date:2019/9/5
 */
public class CyclicBarrierTest implements Runnable{
    private CyclicBarrier cyclicBarrier;
    public CyclicBarrierTest(CyclicBarrier cyclicBarrier) {
        this.cyclicBarrier = cyclicBarrier;
    }
    public void run() {
        try {
            System.out.println(Thread.currentThread().getName() + "進度條100%... ");
            cyclicBarrier.await();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public static void main(String[] args) throws Exception {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(11, new Runnable() {//11個是因為還有一個主執行緒也在等待
                    public void run() {
                        System.out.println("所有人進度條100%,準備開始遊戲");
                    }
                });
        for (int i = 0; i < 10; i++) {
            new Thread(new CyclicBarrierTest(cyclicBarrier), "執行緒" + i).start();
        }
        cyclicBarrier.await();
        Thread.sleep(300);
        System.out.println("開始遊戲....");
    }

}

 

原理解析

1.預設每個CyclicBarrier物件有一把鎖ReentrantLock和Condition

 

2.將構造方法的入引數量賦值到count欄位中。後續都是在count欄位上面進行操作。

 

3.await的呼叫會將count的數量-1,如果扣減到0.則會先執行構造方法傳入的任務(如果傳了),並且重置計數器重新整理柵欄,將許可資料重新賦值給count欄位(可以重複使用),喚醒條件等待的執行緒

 

 

 

 4.如果扣減完成之後還沒有到0.說明還有執行緒沒有到達柵欄點。則進入條件佇列阻塞等到,等到最後一個到達時候,才被喚醒

 

 

 兩者比較

CountDownLatch和CyclicBarrier,最終實現效果看起來都差不多,都是等待分支執行緒執行完畢,再往下執行。然後CyclicBarrier這個可以重複使用,因為會去重新整理count的數量。CountDownLatch不會重新重新整理state欄位的值。當第二次await執行的時候一看state是0就直接放行了,所以一個CountDownLatch物件只能使用一次。

 

原理上CountDownLatch是阻塞主執行緒,分支線執行緒執行完畢將state扣減到0了之後喚醒主執行緒去執行,CyclicBarrier則是所有執行緒到達柵欄點都會阻塞等待。直到後一個到達才喚醒所有的阻塞執行緒。

 

相關文章