概述
CountDownLatch其实就是一个计数器,用来同步一个或多个任务,强制任务按步骤执行,多用来协调多线程。也就是一个任务想要执行,必须要等其他任务执行完后才能执行。
CountDownLatch需要设置任务初始值,也就是需要执行的任务需要分几步执行,初始值就设为几,当计数值不为0的时候,任何一个对象调用wait()都将被阻塞,每一个步骤执行完毕后,调用countDown()计数都会减一。
调用countDown()的任务在调用时没有被阻塞,只有调用wait()才会被阻塞,一直到计数值为0。
该思想并非只在并发中使用,实际开发中很多场景也会使用相似的方法。
使用
方法解读
CountDownLatch非常简单,只有几个方法。
- await(), 等待计数器的值为0,若为0就返回,如果不为0就阻塞。
- await(long timeout, TimeUnit unit),在指定时间内等待计数器的值为0,若为0,则返回true,若不为0,则返回false。
- countDown(),计数减1,若减1之前为1,则置为0,且唤醒所有等待的线程;若减1之前为0,则啥也不做。
- getCount(),获取当前计数的值。
使用
- 创建线程。
产品经理:
public class ProductManager extends Thread {
private CountDownLatch countDown;
public ProductManager(CountDownLatch countDown) {
this.countDown = countDown;
}
@Override
public void run() {
if(countDown.getCount() != 4){
try {
countDown.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("俺是产品经理,俺是提需求的");
countDown.countDown();
}
}
UI设计:
public class UI extends Thread {
private CountDownLatch countDown;
public UI(CountDownLatch countDown) {
this.countDown = countDown;
}
@Override
public void run() {
if(countDown.getCount() != 3){
try {
countDown.await(1000, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("俺是设计,俺是设计界面的");
countDown.countDown();
}
}
开发:
public class Developer extends Thread {
private CountDownLatch countDown;
public Developer(CountDownLatch countDown) {
this.countDown = countDown;
}
@Override
public void run() {
if(countDown.getCount() != 2){
try {
countDown.await(2000, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("俺是开发,俺是干活的");
countDown.countDown();
}
}
测试:
public class Test extends Thread {
private CountDownLatch countDown;
public Test(CountDownLatch countDown) {
this.countDown = countDown;
}
@Override
public void run() {
if(countDown.getCount() != 1){
try {
countDown.await(3000, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("俺是测试,俺是保证产品质量的");
countDown.countDown();
}
}
- 按指定步骤执行。
public static void main(String[] args) {
CountDownLatch countDownLatch = new CountDownLatch(4);
// UI设计
UI ui = new UI(countDownLatch);
ui.start();
// 产品经理
ProductManager productManager = new ProductManager(countDownLatch);
productManager.start();
// 开发
Developer developer = new Developer(countDownLatch);
developer.start();
// 测试
Test test = new Test(countDownLatch);
test.start();
//
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("产品可以上线了");
}
- 结果。
俺是产品经理,俺是提需求的
俺是设计,俺是设计界面的
俺是开发,俺是干活的
俺是测试,俺是保证产品质量的
产品可以上线了
总结
CountDownLatch强调是其他任务执行完后,再执行指定任务,其他任务的执行顺序无法保证,当然可以多用几个CountDownLatch,也可以使用join()。
join可以保证线程按顺序执行,即使线程睡眠。
try {
productManager.start();
productManager.join();
ui.start();
ui.join();
developer.start();
developer.join();
test.start();
test.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
执行结果是一样的。
CountDownLatch中线程睡眠,执行顺序就不一样了。
ProductManager 睡眠1000ms。
public class ProductManager extends Thread {
private CountDownLatch countDown;
public ProductManager(CountDownLatch countDown) {
this.countDown = countDown;
}
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("俺是产品经理,俺是提需求的");
countDown.countDown();
}
}
Developer睡眠3000ms。
执行:
CountDownLatch countDownLatch = new CountDownLatch(4);
// UI设计
UI ui = new UI(countDownLatch);
// 产品经理
ProductManager productManager = new ProductManager(countDownLatch);
// 开发
Developer developer = new Developer(countDownLatch);
// 测试
Test test = new Test(countDownLatch);
productManager.start();
ui.start();
developer.start();
test.start();
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("产品可以上线了");
结果:
俺是设计,俺是设计界面的
俺是测试,俺是保证产品质量的
俺是产品经理,俺是提需求的
俺是开发,俺是干活的
产品可以上线了
CountDownLatch只能是只触发一次的事件。若是需要多次触发,就需要使用CyclicBarrier。