关于CountDownLatch
- CountDownLatch 是什么
- CountDownLatch 如何工作
- CountDownLatch API
- 使用示例
- 与 Join 的区别
CountDownLatch 是什么
CountDownLatch这个类能够使一个线程等待其他线程完成各自的工作后再执行。例如,应用程序的主线程希望在负责启动框架服务的线程已经启动所有的框架服务之后再执行。
使用场景:
Zookeeper分布式锁,Jmeter模拟高并发等。
CountDownLatch 如何工作
CountDownLatch 是通过一个计数器来实现的,计数器的初始值为线程的数量。每当一个线程完成了自己的任务后调用 countDown(),计数器的值就会减1。当计数器值到达0时,它表示所有的线程已经完成了任务,被 await() 方法阻塞的线程才会被释放。
CountDownLatch API
使用示例
假设一条流水线上有三个工作者:worker0,worker1,worker2。有一个任务的完成需要他们三者协作完成,worker2可以开始这个任务的前提是 worker0 和 worker1 完成了他们的工作,而 worker0 和worker1 是可以并行他们各自的工作的。
接下来我们创建一个计数器为 2 的 CountDownLatch 实例 ,让Worker 持有这个 CountDownLatch 实例,当完成自己的工作后,调用countDownLatch. countDown() 方法将计数器减1。countDownLatch.await() 方法会一直阻塞直到计数器为0,主线程才会继续往下执行。
Worker 类如下:
package com.concurrent.test4;
import java.util.concurrent.CountDownLatch;
/**
* 工作者类
* @author ThinkPad
*
*/
public class Worker extends Thread {
//工作者名
private String name;
//工作时间
private long time;
private CountDownLatch countDownLatch;
public Worker(String name, long time, CountDownLatch countDownLatch) {
this.name = name;
this.time = time;
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
try {
System.out.println(name+"开始工作");
Thread.sleep(time);
System.out.println(name+"工作完成,耗费时间="+time);
countDownLatch.countDown();
System.out.println("countDownLatch.getCount()="+countDownLatch.getCount());
} catch (InterruptedException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
}
Test 类如下:
package com.concurrent.test4;
import java.util.concurrent.CountDownLatch;
public class Test {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(2);
Worker worker0 = new Worker("worker0", (long) (Math.random()*2000+3000), countDownLatch);
Worker worker1 = new Worker("worker1", (long) (Math.random()*2000+3000), countDownLatch);
Worker worker2 = new Worker("worker2", (long) (Math.random()*2000+3000), countDownLatch);
worker0.start();
worker1.start();
countDownLatch.await();
System.out.println("准备工作就绪");
worker2.start();
}
}
观察运行结果:
worker1开始工作
worker0开始工作
worker0工作完成,耗费时间=3174
countDownLatch.getCount()=1
worker1工作完成,耗费时间=3870
countDownLatch.getCount()=0
准备工作就绪
worker2开始工作
worker2工作完成,耗费时间=3992
与 Join 的区别
除了 CountDownLatch 外,我们还可以使用 Join 来模拟以上场景。
当在当前线程中调用某个线程 thread 的 join() 方法时,当前线程就会阻塞,直到 thread 执行完成,当前线程才可以继续往下执行。
补充下:join 的工作原理是,不停检查 thread 是否存活,如果存活则让当前线程永远 wait,直到 thread 线程终止,线程的 this.notifyAll() 就会被调用。
接下来使用 Join 来实现一下。
Worker 类如下:
package com.concurrent.test3;
/**
* 工作者类
* @author ThinkPad
*
*/
public class Worker extends Thread {
//工作者名
private String name;
//工作时间
private long time;
public Worker(String name, long time) {
this.name = name;
this.time = time;
}
@Override
public void run() {
// TODO 自动生成的方法存根
try {
System.out.println(name+"开始工作");
Thread.sleep(time);
System.out.println(name+"工作完成,耗费时间="+time);
} catch (InterruptedException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
}
Test 类如下:
package com.concurrent.test3;
public class Test {
public static void main(String[] args) throws InterruptedException {
Worker worker0 = new Worker("worker0", (long) (Math.random()*2000+3000));
Worker worker1 = new Worker("worker1", (long) (Math.random()*2000+3000));
Worker worker2 = new Worker("worker2", (long) (Math.random()*2000+3000));
worker0.start();
worker1.start();
worker0.join();
worker1.join();
System.out.println("准备工作就绪");
worker2.start();
}
}
观察运行结果:
worker1开始工作
worker0开始工作
worker1工作完成,耗费时间=3947
worker0工作完成,耗费时间=4738
准备工作就绪
worker2开始工作
worker2工作完成,耗费时间=4513
可以发现 Join 确实可以完成这个需求,那么它和 CountDownLatch 有哪些区别呢?
目前所知的 CountDownLatch 与 Join 的不同点:
Join 会不停检查 thread 是否存活,如果存活则让当前线程永远 wait,直到 thread 线程终止,线程的 this.notifyAll() 就会被调用。
CountDownLatch 通过计数器是否为 0 决定是否释放资源,并不需要线程终止才会去释放资源。
可见 CountDownLatch 比 Join 要灵活许多,我们使用另一种场景来说明这个问题。
假设 worker 的工作可以分为两个阶段,work2 只需要等待 work0和 work1 完成他们各自工作的第一个阶段之后就可以开始自己的工作了,而不是场景1中的必须等待 work0 和 work1 把他们的工作全部完成之后才能开始。
这种情况是 Join 无法实现的,而 CountDownLatch 却很轻松。我们可以在 worker0 和 worker1 完成第一阶段工作之后就 调用 countDown() 把计数器减 1 即可,这样 worker0 和 worker1 在完成第一阶段工作之后,worker2 就可以开始工作了。
Worker 类如下:
package com.concurrent.test5;
import java.util.concurrent.CountDownLatch;
/**
* 工作者类
* @author ThinkPad
*
*/
public class Worker extends Thread {
//工作者名
private String name;
//第一阶段工作时间
private long time;
private CountDownLatch countDownLatch;
public Worker(String name, long time, CountDownLatch countDownLatch) {
this.name = name;
this.time = time;
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
try {
System.out.println(name+"开始工作");
Thread.sleep(time);
System.out.println(name+"第一阶段工作完成");
countDownLatch.countDown();
Thread.sleep(2000); //这里就姑且假设第二阶段工作都是要2秒完成
System.out.println(name+"第二阶段工作完成");
System.out.println(name+"工作完成,耗费时间="+(time+2000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Test 类如下:
package com.concurrent.test5;
import java.util.concurrent.CountDownLatch;
public class Test {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(2);
Worker worker0 = new Worker("worker0", (long) (Math.random()*2000+3000), countDownLatch);
Worker worker1 = new Worker("worker1", (long) (Math.random()*2000+3000), countDownLatch);
Worker worker2 = new Worker("worker2", (long) (Math.random()*2000+3000), countDownLatch);
worker0.start();
worker1.start();
countDownLatch.await();
System.out.println("准备工作就绪");
worker2.start();
}
}
观察运行结果:
worker0开始工作
worker1开始工作
worker1第一阶段工作完成
worker0第一阶段工作完成
准备工作就绪
worker2开始工作
worker1第二阶段工作完成
worker1工作完成,耗费时间=5521
worker0第二阶段工作完成
worker0工作完成,耗费时间=6147
worker2第一阶段工作完成
worker2第二阶段工作完成
worker2工作完成,耗费时间=5384
如此,变可以看出 Join 的短板。