天天看點

線程 | 并發工具的使用-CountDownLatch線程-并發工具-CountDownLatch一、CountDownLatch是什麼?二、主要參數與方法三、原理四、實踐總結

線程-并發工具-CountDownLatch

文章目錄

  • 線程-并發工具-CountDownLatch
  • 一、CountDownLatch是什麼?
  • 二、主要參數與方法
    • 1.主要方法
    • 2.構造方法
  • 三、原理
    • 1.核心方法源碼
  • 四、實踐
    • 1.用法一:一個線程等待其他多個線程都執行完畢,再繼續自己的工作
    • 2.用法二:多個線程等待某一個線程的信号,同時開始執行
  • 總結
    • 注意點

一、CountDownLatch是什麼?

CountDownLatch是一種并發流程控制的同步工具。主要的作用是等待多個線程同時完成任務之後,再繼續完成主線程任務。簡單點可以了解為,幾個小夥伴一起到火鍋店聚餐,人到齊了,火鍋店才可以開飯。

二、主要參數與方法

1.主要方法

count 數量,了解為小夥伴的個數

//減少鎖存器的計數,如果計數達到零,則釋放所有等待線程。
//計數器
public void countDown() {
    sync.releaseShared(1);
}

//導緻目前線程等待,直到鎖存器遞減至零為止,除非該線程被中斷。
//火鍋店調用await的線程,count為0才能繼續執行
public void await() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}
           

2.構造方法

代碼如下:

//初始化值
public CountDownLatch(int count) {
    if (count < 0) throw new IllegalArgumentException("count < 0");
    this.sync = new Sync(count);
}

//擷取剩餘的數量
public long getCount() {
    return sync.getCount();
}
           

三、原理

我們可以看出countDown()是CountDownLatch的核心方法,我來看下他的具體實作。

線程 | 并發工具的使用-CountDownLatch線程-并發工具-CountDownLatch一、CountDownLatch是什麼?二、主要參數與方法三、原理四、實踐總結

CountDownLatch來時繼承AQS的共享模式來完成其的實作,從前面的學習得出AQS主要是依賴同步隊列和state實作控制。

什麼是共享模式?

這裡與獨占鎖大多數相同,自旋過程中的退出條件是是目前節點的前驅節點是頭結點并且tryAcquireShared(arg)傳回值大于等于0即能成功獲得同步狀态.

1.核心方法源碼

await

public boolean await(long timeout, TimeUnit unit)
    throws InterruptedException {
    return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
//當任務數量不為0挂起,線程排隊
protected int tryAcquireShared(int acquires) {
    return (getState() == 0) ? 1 : -1;
}
//在共享模式下擷取
doAcquireSharedInterruptibly(int arg)
           

countDown

public void countDown() {
    sync.releaseShared(1);
}

protected boolean tryReleaseShared(int releases) {
    // Decrement count; signal when transition to zero
    //自旋防止失敗
    for (;;) {
        //擷取剩餘的任務數量
        int c = getState();
        //任務為0傳回false
        if (c == 0)
            return false;
            //調用cas來進行替換,也保證了線程安全,當為0的時候喚醒
        int nextc = c-1;
        if (compareAndSetState(c, nextc))
            return nextc == 0;
    }
}
//當任務數量為0,aqs的釋放共享鎖
void doReleaseShared()
           

四、實踐

1.用法一:一個線程等待其他多個線程都執行完畢,再繼續自己的工作

public class CountDownLatchTest {

    private static Lock lock = new ReentrantLock();
    private static CountDownLatch countDownLatch = new CountDownLatch(4);


    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(4);
        IntStream.range(0,4).forEach(i ->{
            executorService.submit(()->{
                lock.lock();
                System.out.println(Thread.currentThread().getName()+ "來火鍋店吃火鍋!");
                try {
                    Thread.sleep(1000);
                    countDownLatch.countDown();
                    System.out.println(Thread.currentThread().getName() + "我到火鍋店了,準備開吃!");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }

            });
        });
        try {
            countDownLatch.await();
            System.out.println("人到齊了,開飯");
            executorService.shutdown();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}
           

輸出結果

線程 | 并發工具的使用-CountDownLatch線程-并發工具-CountDownLatch一、CountDownLatch是什麼?二、主要參數與方法三、原理四、實踐總結

代碼中設定了一個CountDownLatch做倒計時,四個人(count為4)一起到火鍋店吃飯,每到一個人計數器就減去1(countDownLatch.countDown()),當計數器為0的時候,main線程在await的阻塞結束,繼續往下執行。

2.用法二:多個線程等待某一個線程的信号,同時開始執行

用搶位子作為例子,将線程挂起等待,同時開始執行。

public class CountDownLatchTest2 {

    private static Lock lock = new ReentrantLock();
    private static CountDownLatch countDownLatch = new CountDownLatch(1);


    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(4);
        IntStream.range(0,4).forEach(i ->{
            executorService.submit(()->{
                System.out.println(Thread.currentThread().getName()+ "準備開始搶位子!");
                try {
                    //Thread.sleep(1000);
                    countDownLatch.await();
                    System.out.println(Thread.currentThread().getName() + "搶到了位置");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        });
        try {
            Thread.sleep(5000);
            System.out.println("五秒後開始搶位置");
            countDownLatch.countDown();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }


        executorService.shutdown();

    }
}
           

總結

我們可以看到CountDownLatch的使用很簡單,就當做一個計時器來使用,在控制并發方面能給我們提供幫助。

  1. 在構造器中初始化任務數量
  2. 調用await()挂起主線程
  3. 調用countDown()方法,減一,直到為0 的時候繼續執行await。

注意點

  1. CountDownLatch是不能重用的。