CountDownLatch 閉鎖(倒計時鎖)

1. CountDownLatch簡介

CountDownLatch :閉鎖(倒計時鎖),JDK 1.5 被引入,位於java.util.concurrent(JUC) 包下。它允許一個或多個線程一直等待,直到其他線程執行完後再執行。具有以下特性:

  • CountDownLatch這個類使一個線程等待其他線程都執行完畢後再執行;
  • 是通過一個計數器count來實現的,計數器的初始值是線程的數量。每當一個線程執行完畢後,計數器的值就-1(調用countDown()方法),當計數器count=0時,表示所有線程都執行完畢,這時在閉鎖上等待的線程就可以恢復工作了。

2. CountDownLatch源碼

public class CountDownLatch {
	
    ......

    private static final class Sync extends AbstractQueuedSynchronizer {

        Sync(int count) {
            setState(count);
        }

        int getCount() {
            return getState();
        }

        ......
        
    }

    private final Sync sync;
    	
    public CountDownLatch(int count) {
	    if (count < 0) throw new IllegalArgumentException("count < 0");
	    this.sync = new Sync(count);
    }

    public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }

    public boolean await(long timeout, TimeUnit unit)
        throws InterruptedException {
        return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
    }

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

    ......
    
}
  • CountDownLatch 和 ReentrantLock 一樣,內部使用 Sync 繼承 AQS;
  • 構造函數將計數值(count)傳遞給 Sync,並且設置了state
  • public CountDownLatch(int count)中的count本質上是閉鎖需要等待的線程數。此值只能設置一次,並且 CountDownLatch 不提供任何其他機制來重置此計數。
  • 主線程/等待執行的線程必須在啓動其他線程後立即調用CountDownLatch.await()方法,這樣主線程/等待執行的線程的操作就會在這個方法上阻塞,直到其他線程完成各自的任務爲止。
  • 其他 N 個線程必須引用閉鎖對象,因爲它們如果完成了任務(線程執行結束)之後需要調用CountDownLatch.countDown() 方法,每次調用countDown()方法後線程計數值count減少1。當所有 N 個線程都執行結束後調用了countDown()方法時,計數將達到 0,主線程/等待執行的線程可以在await() 方法之後繼續執行。

3. CountDownLatch簡單使用

業務需求:用10個線程分別輸出50000以內的偶數,並在這10個線程運行結束之後再進行其他操作(此處的操作是打印耗費的時間,即在10個線程結束之後打印耗費的時間)

1.1 使用沒有CountDownLatch的方法測試是否能實現

public class NoCountDownLatchTest {

    public static void main(String[] args) {
        LatchDemo latchDemo = new LatchDemo();
        long startTime = System.currentTimeMillis();

        for(int i=0;i<10;i++){
            new Thread(latchDemo).start();
        }

        long endTime = System.currentTimeMillis();

        System.out.println("耗費時間爲"+ (endTime-startTime));
    }

}

class LatchDemo implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 50000; i++) {
            if (i % 2 == 0) {
                System.out.println(i);
            }
        }
    }

}

輸出結果如下:
在這裏插入圖片描述
分析:從輸出結果來看並沒有實現預期效果,在10個線程結束之前耗費的時間就已經輸出了。下面測試使用CountDownLatch實現的效果。

1.2 使用CountDownLatch的方法測試是否能實現

import java.util.concurrent.CountDownLatch;

public class CountDownLatchTest {

    public static void main(String[] args) throws InterruptedException {
        final CountDownLatch countDownLatch = new CountDownLatch(10);
        LatchDemo latchDemo = new LatchDemo(countDownLatch);

        long startTime = System.currentTimeMillis();

        for(int i=0;i<10;i++){
            new Thread(latchDemo).start();
        }

        //等待其他線程全部完成再向下執行代碼
        countDownLatch.await();

        long endTime = System.currentTimeMillis();

        System.out.println("耗費時間爲"+ (endTime-startTime));
    }

}

class LatchDemo implements Runnable{

    private CountDownLatch countDownLatch;

    public LatchDemo(CountDownLatch latch){
        this.countDownLatch = latch;
    }

    @Override
    public void run() {
        try {
            for (int i = 0; i < 50000; i++) {
                if (i % 2 == 0) {
                    System.out.println(i);
                }
            }
        }finally {
            //finally保證countDown()方法一定執行
            countDownLatch.countDown();
        }
    }
}

運行結果:
在這裏插入圖片描述
分析:由輸出結果可以看出,輸出結束時間是在這10個線程執行結束之後才執行的,並沒有在線程結束之前執行。達到了預期效果。

4. 總結

  • CountDownLatch 是一次性的,計數器的值只能在構造方法中初始化一次,之後沒有任何機制再次對其設置值,當 CountDownLatch 使用完畢後,不能再次被使用。
  • Thread 的 join()方法可以實現相同的功能,但是當使用了線程池時, join() 方法便無法實現,CountDownLatch 依然可以實現功能。

參考鏈接:https://www.jianshu.com/p/adcf9ab6161b

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章