文章目錄
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