CountDownLatch是什麼?
CountDownLatch是在java1.5被引入的,跟它一起被引入的併發工具類還有CyclicBarrier、Semaphore、ConcurrentHashMap和BlockingQueue,它們都存在於java.util.concurrent包下。CountDownLatch這個類能夠使一個線程等待其他線程完成各自的工作後再執行。例如,應用程序的主線程希望在負責啓動框架服務的線程已經啓動所有的框架服務之後再執行。
CountDownLatch是通過一個計數器來實現的,計數器的初始值爲線程的數量。每當一個線程完成了自己的任務後,計數器的值就會減1。當計數器值到達0時,它表示所有的線程已經完成了任務,然後在閉鎖上等待的線程就可以恢復執行任務。
CountDownLatch如何工作
CountDownLatch.java類中定義的構造函數:
public CountDownLatch(int count) |
構造器中的計數值(count)實際上就是閉鎖需要等待的線程數量。這個值只能被設置一次,而且CountDownLatch沒有提供任何機制去重新設置這個計數值。
與CountDownLatch的第一次交互是主線程等待其他線程。主線程必須在啓動其他線程後立即調用CountDownLatch.await()方法。這樣主線程的操作就會在這個方法上阻塞,直到其他線程完成各自的任務。
其他N 個線程必須引用閉鎖對象,因爲他們需要通知CountDownLatch對象,他們已經完成了各自的任務。這種通知機制是通過 CountDownLatch.countDown()方法來完成的;每調用一次這個方法,在構造函數中初始化的count值就減1。所以當N個線程都調 用了這個方法,count的值等於0,然後主線程就能通過await()方法,恢復執行自己的任務。
簡單示例
開始示例代碼之前我們通過上面的瞭解發現CountDownLatch的作用完全可以使用join替代實現,實質作用就是讓線程之間等待嘛。那麼,CountDownLatch真的是畫蛇添足嘛?帶着這個疑問我們看下面場景:
場景一:學生寫完作業之後老師開始批作業使用join實現:
public class CountDownLatchTest {
public static void main(String[] args) throws InterruptedException {
People stu1 = new People("小明");
People stu2 = new People("小紅");
People teacher = new People("張老師");
stu1.start();
stu2.start();
stu1.join();
stu2.join();
System.out.println("學生寫完作業,老師可以開始檢查了~");
teacher.start();
}
}
class People extends Thread {
private String name;
public People(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println(name + "開始了工作");
}
}
2. 使用CountDownLatch實現:
public class CountDownLatchTest {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(2);
People stu1 = new People("小明",countDownLatch);
People stu2 = new People("小紅",countDownLatch);
People teacher = new People("張老師",countDownLatch);
stu1.start();
stu2.start();
countDownLatch.await();
System.out.println("學生寫完作業,老師可以開始檢查了~");
teacher.start();
}
}
class People extends Thread {
private String name;
private CountDownLatch countDownLatch;
public People(String name, CountDownLatch countDownLatch) {
this.name = name;
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
try {
System.out.println(name + "開始了工作");
Thread.sleep((long) (Math.random()*2000));
System.out.println(name + "結束了工作");
} catch (InterruptedException e) {
e.printStackTrace();
}
countDownLatch.countDown();
}
}
這樣看來兩者都可以實現這個場景。
場景二:學生寫完作業之後就可以給老師看作業,學生回家先看代碼
public class CountDownLatchTest {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(2);
People stu1 = new People("小明", countDownLatch);
People stu2 = new People("小紅", countDownLatch);
People teacher = new People("張老師", countDownLatch);
stu1.start();
stu2.start();
countDownLatch.await();
System.out.println("學生寫完作業,老師可以開始檢查了~");
teacher.start();
}
}
class People extends Thread {
private String name;
private CountDownLatch countDownLatch;
public People(String name, CountDownLatch countDownLatch) {
this.name = name;
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
//假設run中有兩塊業務邏輯,先是寫作業,然後是放學回家
try {
System.out.println(name + "開始了工作");
Thread.sleep((long) (Math.random() * 2000));
System.out.println(name + "結束了工作");
countDownLatch.countDown();
Thread.sleep(2000);
System.out.println(name + "放學回家玩LOL了~");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
結果:
小紅開始了工作
小明開始了工作
小明結束了工作
小紅結束了工作
學生寫完作業,老師可以開始檢查了~
張老師開始了工作
張老師結束了工作
小明放學回家玩LOL了~
小紅放學回家玩LOL了~
張老師放學回家玩LOL了~
總結一下兩者區別:調用thread.join() 方法必須等thread 執行完畢,當前線程才能繼續往下執行,而CountDownLatch通過計數器提供了更靈活的控制,只要檢測到計數器爲0當前線程就可以往下執行而不用管相應的thread是否執行完畢。