Latch(阀门)设计模式也叫做 Count Down 设计模式。当若干个线程并发执行完某个特 定的任务,然后等到所有的子任务都执行结束之后再统一汇总。
CountDownLatch 能够使一个线程在等待另外一些线程完成各自工作之后,再继续执 行。使用一个计数器进行实现。计数器初始值为线程的数量。当每一个线程完成自己任务后, 计数器的值就会减一。当计数器的值为 0 时,表示所有的线程都已经完成了任务,然后在 CountDownLatch 上等待的线程就可以恢复执行任务。示例代码如下:
代码1:封装自己的CountDownLatch
package com.bjsxt.chapter22;
public class CustomCountDownLatch {
private volatile int count;
public CustomCountDownLatch(int count){
this.count=count;
}
public void countDown(){
synchronized (this){
this.count--;
System.out.println(Thread.currentThread().getName()+" will end.");
this.notifyAll();// 只会唤醒 main线程
}
}
public void await(){
synchronized (this){
try{
while(count!=0){
System.out.println(Thread.currentThread().getName()+" will wait.");// 被唤醒了5次
this.wait();
System.out.println(Thread.currentThread().getName()+" will go on.");
}
}catch (Exception e){
e.printStackTrace();
}
}
}
}
代码2:外部调用的测试类
package com.bjsxt.chapter22;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
public class Main {
public static void main(String[] args) {
// main ,t1,t2,...t5 6个线程共享资源 count,countDoenLatch
int count=5;
final CustomCountDownLatch countDownLatch=new CustomCountDownLatch(count);
for(int i=1;i<=count;i++){
new Thread(()->{
try {
// 它没有占用锁资源
TimeUnit.SECONDS.sleep(ThreadLocalRandom.current().nextInt(30));// 模拟线程执行时间 0~9 s
// t1,t2,...,t5 线程执行完毕后countDownLatch.count数量减一
// countDown 占用锁资源 countDownLatch
countDownLatch.countDown();
}catch (Exception e){
e.printStackTrace();
}
},"t"+i).start();
}
// main 线程查询countDownLatch.count 为 0 就启动,不为 0 就进入wait队列的阻塞状态,等待被唤醒
countDownLatch.await();
// do sth
System.out.println("元宵节快乐吖");
}
}
运行结果:
分析:
开始的时候写的是if语句,不是while循环。一个线程执行完毕后,main线程就会输出结果。如下图。改成while循环就好了。
根本原因是t1,至t5处于线程普通的阻塞状态,过几秒进入Runnable状态。只有main线程处于wait队列的阻塞状态,被其他线程notify后会进入锁池,只有他会竞争CountDownLatch锁,竞争上就进入Runnable状态。
public void await(){
synchronized (this){
try{
if(count!=0)
System.out.println(Thread.currentThread().getName()+" will wait.");// 被唤醒了5次
this.wait();
System.out.println(Thread.currentThread().getName()+" will go on.");
}
}catch (Exception e){
e.printStackTrace();
}
}
}
结果
查看这六个线程某一时间的jstack,验证分析的是否符合实际:t1至t5的阻塞状态只能是普通的阻塞状态。main是wait队列的阻塞状态。
2021-02-26 14:46:20
main
t1,至t5
t2线程处于Terminated状态,其他线程陷入普通的阻塞状态。
2021-02-26 14:46:22:
main:处于wait队列的阻塞状态
t1至t5
t2,t4处于Terminated状态,其他县城处于普通的阻塞状态。
2021-02-26 14:46:29:
main:处于wait队列的阻塞状态
t1至t5:
t2,t4,t5依次进入了Terminated状态;其他县城处于普通的阻塞状态。
2021-02-26 14:46:33:
main:处于wait队列的阻塞状态
t1至t5:
t2,t4,t5,t1依次进入了Terminated状态;t3处于普通的阻塞状态
控制台的输出信息:导致输出2,4,5,1,3顺序结束。每次进入countDown方法,执行notifyAll,实际上只会唤醒main线程,所以会输出5次
main will wait和mainwill go on