在多线程编程时,常常需要解决线程同步问题,在上一节讲得BlockQueue的take和put方法,是通过阻塞来控制生产者和消费者执行流程,它其实也是在解决线程同步问题。另外在线程同步问题还可以通过很多方式解决比如信号量,栅栏,闭锁。今天所说的CountDownLatch就是一种闭锁同步类,它的作用就像一扇门,在达到结束状态之前门是关闭的,并且没有任何线程可以通过这扇门,当达到了结束状态,门打开,所有的线程可以通过。并且结束后,不会再关闭。
CountDownLatch简介
CountDownLatch是一种闭锁的实现,它通过一个计数器来实现。
1、在初始化时,指定等待线程的数量。
2、每个线程结束时,调用countDown将等待数量减1
3、它会有一个await方法,来等待线程全部执行结束。
CountDownLatch用法
/**
CountDownLatch类是一个同步计数器,构造时传入int参数,该参数就是计数器的初始值,每调用一次countDown()方法,计数器减1,计数器大于0 时,await()方法会阻塞程序继续执行
CountDownLatch如其所写,是一个倒计数的锁存器,当计数减至0时触发特定的事件。利用这种特性,可以让主线程等待子线程的结束。下面以一个模拟运动员比赛的例子加以说明。
*/
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CountDownLatchDemo {
private static final int PLAYER_AMOUNT = 5;
public CountDownLatchDemo() {
// TODO Auto-generated constructor stub
}
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
//对于每位运动员,CountDownLatch减1后即结束比赛
CountDownLatch begin = new CountDownLatch(1);
//对于整个比赛,所有运动员结束后才算结束
CountDownLatch end = new CountDownLatch(PLAYER_AMOUNT);
Player[] plays = new Player[PLAYER_AMOUNT];
for(int i=0;i<PLAYER_AMOUNT;i++)
plays[i] = new Player(i+1,begin,end);
//设置特定的线程池,大小为5
ExecutorService exe = Executors.newFixedThreadPool(PLAYER_AMOUNT);
for(Player p:plays)
exe.execute(p); //分配线程
System.out.println("Race begins!");
begin.countDown();
try{
end.await(); //等待end状态变为0,即为比赛结束
}catch (InterruptedException e) {
// TODO: handle exception
e.printStackTrace();
}finally{
System.out.println("Race ends!");
}
exe.shutdown();
}
}
接下来是Player类
import java.util.concurrent.CountDownLatch;
public class Player implements Runnable {
private int id;
private CountDownLatch begin;
private CountDownLatch end;
public Player(int i, CountDownLatch begin, CountDownLatch end) {
// TODO Auto-generated constructor stub
super();
this.id = i;
this.begin = begin;
this.end = end;
}
@Override
public void run() {
// TODO Auto-generated method stub
try{
begin.await(); //等待begin的状态为0
Thread.sleep((long)(Math.random()*100)); //随机分配时间,即运动员完成时间
System.out.println("Play"+id+" arrived.");
}catch (InterruptedException e) {
// TODO: handle exception
e.printStackTrace();
}finally{
end.countDown(); //使end状态减1,最终减至0
}
}
}
代码以及使用场景示例
假设如下场景:在一个批处理程序中,一次从一个远程接口中,拉取数据需要耗时非常长的时间,这时考虑到使用多线程去拉,可以减少线程等待的时间。这时就可以使用CountDownLatch来做线程同步,来保证每个线程都执行完成来继续下面的流程。
public class CountDownLatchDemo {
private static ExecutorService executor= Executors.newFixedThreadPool(5);
public static void main(String[] args){
CountDownLatch countDownLatch=new CountDownLatch(10);
for(int index=0;index<10;index++) {
executor.execute(new Task(index*10,(index+1)*10,countDownLatch));
}
try {
System.out.println("开始等待线程执行...");
countDownLatch.await();
System.out.println("全部线程执行完毕");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private static class Task implements Runnable{
private int startIndex;
private int count;
private CountDownLatch countDownLatch;
public Task(int startIndex, int count,CountDownLatch countDownLatch) {
this.startIndex = startIndex;
this.count = count;
this.countDownLatch=countDownLatch;
}
@Override
public void run() {
try {
Thread.sleep((long) (1000 * Math.random()));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getId()+"执行完成。");
countDownLatch.countDown();
}
}
}
以上代码模拟了上述假设中得场景。其中使用了线程池来做线程管理,这些对象也将在后续介绍。
输出结果如下:
开始等待线程执行...
12执行完成。
11执行完成。
12执行完成。
11执行完成。
13执行完成。
13执行完成。
9执行完成。
10执行完成。
12执行完成。
11执行完成。
全部线程执行完毕