前言
利用CountDownLatch进行线程间同步,可以控制其线程的执行时序,谁先执行、谁后执行,利用好CountDownLatch即可
举例
需求:共计3个线程参与,1个主线程、2个工作线程,主线程等待另外2个工作线程完成任务后,主线程再继续执行,这就是线程间的同步
package com.wp.blockthread;
import java.util.Date;
import java.util.concurrent.CountDownLatch;
public class CountDownDemo {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch=new CountDownLatch(2); //主线程中创建CountDownLatch对象,计数器为2
DressingThread firstGirl=new DressingThread("Alisa", 5000, latch); //一号工作线程,持有同一个CountDownLatch对象
DressingThread secondGirl=new DressingThread("Lily", 4000, latch); //二号工作线程,持有同一个CountDownLatch对象
firstGirl.start();
secondGirl.start();
latch.await(); //主线程阻塞在此行,当CountDownLatch对象的计数为0时,主线程才能继续执行
System.out.println("All girls dressing done at "+ new Date(System.currentTimeMillis()));
}
static class DressingThread extends Thread{
String girlName;
int dressingDuration;
CountDownLatch latch; //当前线程对象负责持有CountDownLatch对象
public Dressing(String girlName , int duration, CountDownLatch latch){
this.girlName =girlName;
this.dressingDuration = duration;
this.latch=latch;
}
public void run(){
System.out.println(girlName +" do dressing begin at "+ getCurrentTime());
doWork();
System.out.println(girlName +" do dressing complete at "+ getCurrentTime());
latch.countDown(); //工作线程调用此方法时,同一个CountDownLatch对象的计数器会减少1
}
private void doWork(){
try {
Thread.sleep(dressingDuration); //线程休眠,模拟工作线程执行任务
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private Date getCurrentTime() {
return new Date(System.currentTimeMillis());
}
}
}
使用过程分析
1、首先在主线程中创建CountDownLatch对象,并传入一个计数器值:2,此时工作线程还未创建
CountDownLatch latch=new CountDownLatch(2); //主线程中创建CountDownLatch对象,计数器为2
2、接着在主线程再创建两个Work Thread对象,同时将同一个CountDownLatch对象的引用传递进去,此时每个Work Thread对象持有的是同一个CountDownLatch对象
DressingThread firstGirl=new DressingThread("Alisa", 5000, latch); //一号工作线程,持有同一个CountDownLatch对象
DressingThread secondGirl=new DressingThread("Lily", 4000, latch); //二号工作线程,持有同一个CountDownLatch对象
3、继续在主线程中开始启动2个工作线程,此时工作线程才开始执行
firstGirl.start();
secondGirl.start();
4、启动完工作线程后,主线程主动调用CountDownLatch对象的await()方法,由于CountDownLatch对象的计数器不为0,该方法会使当前主线程进入阻塞状态,这样就可以等待工作线程的执行了
latch.await(); //主线程阻塞在此行,只有当CountDownLatch对象的计数为0时,主线程才能继续执行
5、每个工作线程的run方法中,会执行业务逻辑,当工作线程中的业务逻辑执行结束,会调用持有的同一个CountDownLatch对象的countDown()方法,每次调用countDown()方法都会将CountDownLatch对象持有的计数器值减去1
public void run(){
System.out.println(girlName +" do dressing begin at "+ getCurrentTime());
doWork();
System.out.println(girlName +" do dressing complete at "+ getCurrentTime());
latch.countDown(); //工作线程调用此方法时,同一个CountDownLatch对象的计数器会减少1
}
6、当CountDownLatch对象持有的计数器等于0时,阻塞的主线程会被唤醒,然后从阻塞代码处继续运行,我们有两个工作线程,都会调用同一个CountDownLatch对象的countDown()方法,所以这个计数器值会成为0
System.out.println("All girls dressing done at "+ new Date(System.currentTimeMillis()));
输出结果
Lily do dressing begin at Thu Jan 17 21:40:35 CST 2019
Alisa do dressing begin at Thu Jan 17 21:40:35 CST 2019
Lily do dressing complete at Thu Jan 17 21:40:39 CST 2019
Alisa do dressing complete at Thu Jan 17 21:40:40 CST 2019
all dressing done at Thu Jan 17 21:40:40 CST 2019
总结
1、CountDownLatch类的内部维护一个初始值,该值称为计数器,主线程执行await()方法时,如果计数器值大于0时,主线程将会被阻塞,当另外的线程完成任务后,调用CountDownLatch对象的coutDown()方法时,计数器值就会减1
2、当这个计数器值等于0时,处于阻塞等待的线程将会被唤醒,则可以继续执行程序了!
3、线程的组合随你搭配,CountDownLatch是线程间同步用的工具类,相当的nice!
4、有没有发现一个风险,如果工作线程因为异常结束,而没有调用CountDownLatch的coutDown()方法,将会导致主线程一直阻塞在await()方法处,你知道怎么规避这种现象吗???卧槽这是个坑啊,所以我们尽量要使用带有timeout时间的等待,而不是死等。