CountDownLatch是在java1.5被引入的,存在于java.util.concurrent包下。CountDownLatch这个类能够使一个线程等待其他线程完成各自的工作后再执行。例如,应用程序的主线程希望在负责启动框架服务的线程已经启动所有的框架服务之后再执行。
CountDownLatch是通过一个计数器来实现的,计数器的初始值为线程的数量。每当一个线程完成了自己的任务后,计数器的值就会减1。当计数器值到达0时,它表示所有的线程已经完成了任务,然后在闭锁上等待的线程就可以恢复执行任务。
1.CountDownLatch的结构
CountDownLatch的UML类图如下:
CountDownLatch的数据结构很简单,它是通过"共享锁"实现的。它包含了sync对象,sync是Sync类型。Sync是实例类,它继承 于AQS。
CountDownLatch类只提供了一个构造器:
public CountDownLatch(int count) { }; //参数count为计数值
构造器中的计数值(count)实际上就是闭锁需要等待的线程数量。这个值只能被设置一次,而且
CountDownLatch没有提供任何机制去重新设置这个计数值。
然后下面这3个方法是CountDownLatch类中最重要的方法:
public void await() throws InterruptedException { };
//调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行
public boolean await(long timeout, TimeUnit unit) throws InterruptedException { };
//和await()类似,只不过等待一定的时间后count值还没变为0的话就会继续执行
public void countDown() { };
//将count值减1
与CountDownLatch的第一次交互是主线程等待其他线程。主线程必须在启动其他线程后立即调用CountDownLatch.await()方法。这样主线程的操作就会在这个方法上阻塞,直到其他线程完成各自的任务。
其他N个线程必须引用闭锁对象,因为他们需要通知CountDownLatch对象,他们已经完成了各自的任务。
这种通知机制是通过 CountDownLatch.countDown()方法来完成的;每调用一次这个方法,在构造函数中
初始化的count值就减1。所以当N个线程都调 用了这个方法,count的值等于0,然后主线程就能通过await()
方法,恢复执行自己的任务。
实现最大的并行性:有时我们想同时启动多个线程,实现最大程度的并行性。
例如,我们想测试一个单例类。如果我们创建一个初始计数为1的CountDownLatch,
并让所有线程都在这个锁上等待,那么我们可以很轻松地完成测试。
我们只需调用 一次countDown()方法就可以让所有的等待线程同时恢复执行。
开始执行前等待n个线程完成各自任务:例如应用程序启动类要确保在处理用户请求前,
所有N个外部系统已经启动和运行了。
死锁检测:一个非常方便的使用场景是,你可以使用n个线程访问共享资源,
在每次测试阶段的线程数目是不同的,并尝试产生死锁。
3.使用示例:
public class Test {
public static void main(String[] args) {
final CountDownLatch latch = new CountDownLatch(2);
new Thread(){
public void run() {
try {
System.out.println("子线程"+Thread.currentThread().getName()+"正在执行");
Thread.sleep(3000);
System.out.println("子线程"+Thread.currentThread().getName()+"执行完毕");
latch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
};
}.start();
new Thread(){
public void run() {
try {
System.out.println("子线程"+Thread.currentThread().getName()+"正在执行");
Thread.sleep(3000);
System.out.println("子线程"+Thread.currentThread().getName()+"执行完毕");
latch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
};
}.start();
try {
System.out.println("等待2个子线程执行完毕...");
latch.await();
System.out.println("2个子线程已经执行完毕");
System.out.println("继续执行主线程");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果:
线程Thread-0正在执行
线程Thread-1正在执行
等待2个子线程执行完毕...
线程Thread-0执行完毕
线程Thread-1执行完毕
2个子线程已经执行完毕
继续执行主线程
用CountDownLatch模拟10人参加跑步比赛的过程。
1. 涉及到的类有Service(模拟跑步),MyThread(模拟一名参赛选手),CountDownLatchDemo(模拟整个跑步比赛场景)。
2. 各个类的详细代码。
(1)Service(模拟跑步)
package test;
public class Service {
public void testMethod() {
try {
System.out.println(Thread.currentThread().getName() + " begin timer " + System.currentTimeMillis());
Thread.sleep((long) (Math.random()*10000));//模拟每个跑步选手跑完100米所需的时间
System.out.println(Thread.currentThread().getName() + " end timer " + System.currentTimeMillis());
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
(2)MyThread(模拟一名参赛选手)
package test;
import java.util.concurrent.CountDownLatch;
public class MyThread extends Thread {
private Service service;
private final CountDownLatch begin;
private final CountDownLatch end;
public MyThread(Service service, CountDownLatch begin, CountDownLatch end){
this.service = service;
this.begin = begin;
this.end = end;
}
public void run(){
try {
begin.await();//每个参赛选手都站在自己的跑道上,做好了比赛的准备,正在等裁判鸣枪开始比赛
service.testMethod();//听到鸣枪后比赛开始了
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
end.countDown();//其中的一个参赛选手已经跑完了
}
}
}
(3)CountDownLatchDemo(模拟整个跑步比赛场景)
package test;
import java.util.concurrent.CountDownLatch;
public class CountDownDemo {
public static void main(String[] args) {
try {
Service service = new Service();
CountDownLatch begin = new CountDownLatch(1);//裁判员鸣枪信号
CountDownLatch end = new CountDownLatch(10);//10名参赛选手结束信号
MyThread[] threadArray = new MyThread[10];
for(int i = 0 ; i < 10; i++){
threadArray[i] = new MyThread(service,begin,end);
threadArray[i].setName((i + 1) + " 号选手 ");
threadArray[i].start();
}
System.out.println("在等待裁判员鸣枪 " + System.currentTimeMillis());
long t1 = System.currentTimeMillis();//记录比赛的开始时间
begin.countDown();//裁判员鸣枪了
end.await();//等待10个参赛选手都跑完100米
long t2 = System.currentTimeMillis();//记录比赛的结束时间
System.out.println("所有参赛选手都完成了100米赛跑,赛跑总耗时是 " + (t2-t1));
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
3. 运行结果
在等待裁判员鸣枪 1521087478233
1 号选手 begin timer 1521087478233
5 号选手 begin timer 1521087478233
7 号选手 begin timer 1521087478233
3 号选手 begin timer 1521087478233
2 号选手 begin timer 1521087478233
10 号选手 begin timer 1521087478233
9 号选手 begin timer 1521087478233
8 号选手 begin timer 1521087478233
4 号选手 begin timer 1521087478233
6 号选手 begin timer 1521087478233
3 号选手 end timer 1521087478435
2 号选手 end timer 1521087480521
4 号选手 end timer 1521087480712
9 号选手 end timer 1521087481602
10 号选手 end timer 1521087482179
7 号选手 end timer 1521087482743
5 号选手 end timer 1521087483620
8 号选手 end timer 1521087485227
1 号选手 end timer 1521087486867
6 号选手 end timer 1521087488049
所有参赛选手都完成了100米赛跑,赛跑总耗时是 9816
总结:CountDownLatch是通过“共享锁”实现的。在创建CountDownLatch中时,会传递一个int类型参数count,
该参数是“锁计数器”的初始状态,表示该“共享锁”最多能被count给线程同时获取。当某线程调用该
CountDownLatch对象的await()方法时,该线程会等待“共享锁”可用时,才能获取“共享锁”进而继续运行。
而“共享锁”可用的条件,就是“锁计数器”的值为0!而“锁计数器”的初始值为count,每当一个线程调用该
CountDownLatch对象的countDown()方法时,才将“锁计数器”-1;通过这种方式,必须有count个线程调用
countDown()之后,“锁计数器”才为0,而前面提到的等待线程才能继续运行!