概念
CountDownLatch是在java1.5被引入的,跟它一起被引入的并发工具类还有CyclicBarrier、Semaphore、ConcurrentHashMap和BlockingQueue,它们都存在于java.util.concurrent包下。CountDownLatch这个类能够使一个线程等待其他线程完成各自的工作后再执行。例如,应用程序的主线程希望在负责启动框架服务的线程已经启动所有的框架服务之后再执行。
CountDownLatch是通过一个计数器来实现的,计数器的初始值为线程的数量。每当一个线程完成了自己的任务后,计数器的值就会减1。当计数器值到达0时,它表示所有的线程已经完成了任务,然后在闭锁上等待的线程就可以恢复执行任务。
作用
CountDownLatch是JAVA提供在java.util.concurrent包下的一个辅助类,可以把它看成是一个计数器,其内部维护着一个count计数,只不过对这个计数器的操作都是原子操作,同时只能有一个线程去操作这个计数器,CountDownLatch通过构造函数传入一个初始计数值,调用者可以通过调用CounDownLatch对象的countDown()方法,来使计数减1;如果调用CounDownLatch对象上的await()方法,那么调用者就会一直阻塞在这里,直到别人通过countDown方法,将计数减到0,才可以继续执行。
CountDownLatch的使用场景:
确保某个计算在其需要的所有资源都被初始化之后才继续执行。
确保某个服务在其依赖的所有其他服务都已启动后才启动。
等待知道某个操作的所有者都就绪在继续执行。
源码解析
//允许一个或多个线程等待,直到*在其他线程中执行的一组操作完成的同步辅助程序。
public class CountDownLatch {
//CountDownLatch的同步控件。使用AQS状态表示计数。这是一个由子类决定含义的“状态”。对于ReentrantLock来说,state是线程获取锁的次数;对于CountDownLatch来说,则表示计数值的大小。
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
Sync(int count) {
setState(count);
}
int getCount() {
return getState();
}
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}
private final Sync sync;
//CountDownLatch和ReentrantLock一样,内部使用Sync继承AQS。构造函数很简单地传递计数值给Sync,在Sync类的构造器中设置state值。
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
//导致当前线程等待直到锁存器计数到*零为止,除非该线程{@linkplain线程#interrupt被中断}。** <p>如果当前计数为零,则此方法立即返回。** <p >如果当前计数大于零,则出于线程调度目的,当前*线程将被禁用,并且处于休眠状态,直到发生以下两种情况之一:* <ul> * //<li>由于调用{ @link #countDown}方法; 或* <li>某些其他线程{@linkplain线程#interrupt中断} *当前线程。* </ ul> ** <p>如果当前线程:* <ul> * <li>的中断状态设置为 进入此方法; 或* <li>在等待时{@linkplain线程#interrupt被中断},* </ ul> *然后抛出{@link //InterruptedException},并清除了当前线程的*中断状态。
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
//导致当前线程等待,直到锁存器计数到*零为止,除非该线程{{linklink Thread#interrupt interrupted},*或指定的等待时间过去。** <p>如果当前计数为零,则此操作方法立即返回*,其值为{@code true}。** //<p>如果当前计数大于零,则出于线程调度目的,当前*线程将被禁用,并且处于休眠状态,直到发生以下三种情况之一:* < ul> * <li>由于调用* {@link #countDown}方法而使计数达到零;或* <li>某些其他线程{@linkplain线程#interrupt中断} *当前线程;或* //<li>经过指定的等待时间。* </ ul> ** <p>如果计数达到零,则该方法返回*值{@code true}。** <p>如果当前线程: * <ul> * <li>在进入此方法时已设置其中断状态;或* <li>在等待时{@linkplain线程#interrupt被中断},* </ ul> *然后抛出{@link //InterruptedException}并清除当前线程的*中断状态。** <p>如果指定了等待时间过后,将返回值{@code false} *。如果时间小于或等于零,则方法*将完全不等待。
public boolean await(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
//减少锁存器的计数,如果*计数达到零,则释放所有等待线程。** <p>如果当前计数大于零,则将其递减。*如果新计数为零,则所有等待线程都将重新执行- 为*线程调度目的而启用。** <p>如果当前计数等于零,则什么也不会发生。
public void countDown() {
sync.releaseShared(1);
}
//返回当前计数。** <p>此方法通常用于调试和测试目的。
public long getCount() {
return sync.getCount();
}
//返回一个标识此锁存器及其状态的字符串。*该状态在方括号中包括字符串{@code“ Count =”} *“,后跟当前计数。
public String toString() {
return super.toString() + "[Count = " + sync.getCount() + "]";
}
}
实例
赛跑
/**
* 举个简单的例子,假如有5个运动员赛跑,为了公平所有运动员需要同时起跑。同时,当最后一个运动员跑完之后,比赛结束。
* 怎么用线程模拟呢?我们假设每个运动员是一个线程,然后只需要通过CountDownLatch就可以简单的实现这个问题。
*/
package com.zyf.eflying.countDownLatch;
import java.util.concurrent.CountDownLatch;
import org.junit.Test;
public class Example {
@Test
public void testCountDownLatch() {
//所有线程阻塞,然后统一开始
CountDownLatch begin = new CountDownLatch(1);
//主线程阻塞,直到所有分线程执行完毕
CountDownLatch end = new CountDownLatch(5);
for(int i=0;i<5;i++) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
begin.await();//所有子线程调用begin.await()方法阻塞
System.out.println(Thread.currentThread().getName() + " 起跑");
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " 到达终点");
end.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
}
try {
System.out.println("1秒后统一开始");
Thread.sleep(1000);
begin.countDown();//所有阻塞的子线程启动
end.await();//主线程阻塞等待所有的子线程执行end.countDown()方法后使得end数量为0时自动释放;
System.out.println("停止比赛");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果:
1秒后统一开始
Thread-0 起跑
Thread-4 起跑
Thread-3 起跑
Thread-2 起跑
Thread-1 起跑
Thread-0 到达终点
Thread-4 到达终点
Thread-3 到达终点
Thread-2 到达终点
Thread-1 到达终点
停止比赛
官方例子1:
class Driver { // ...
void main() throws InterruptedException {
CountDownLatch startSignal = new CountDownLatch(1);
CountDownLatch doneSignal = new CountDownLatch(N);
for (int i = 0; i < N; ++i) // create and start threads
new Thread(new Worker(startSignal, doneSignal)).start();
doSomethingElse(); // don't let run yet
startSignal.countDown(); // let all threads proceed
doSomethingElse();
doneSignal.await(); // wait for all to finish
}
}
class Worker implements Runnable {
private final CountDownLatch startSignal;
private final CountDownLatch doneSignal;
Worker(CountDownLatch startSignal, CountDownLatch doneSignal) {
this.startSignal = startSignal;
this.doneSignal = doneSignal;
}
public void run() {
try {
startSignal.await();
doWork();
doneSignal.countDown();
} catch (InterruptedException ex) {} // return;
}
void doWork() { ... }
}
官方例子2:
class Driver2 { // ...
void main() throws InterruptedException {
CountDownLatch doneSignal = new CountDownLatch(N);
Executor e = ...
for (int i = 0; i < N; ++i) // create and start threads
e.execute(new WorkerRunnable(doneSignal, i));
doneSignal.await(); // wait for all to finish
}
}
class WorkerRunnable implements Runnable {
private final CountDownLatch doneSignal;
private final int i;
WorkerRunnable(CountDownLatch doneSignal, int i) {
this.doneSignal = doneSignal;
this.i = i;
}
public void run() {
try {
doWork(i);
doneSignal.countDown();
} catch (InterruptedException ex) {} // return;
}
void doWork() { ... }
}
常见面试题
1CountDownLatch 和CyclicBarrier的不同之处?
(1)CountDownLatch和CyclicBarrier都能够实现线程之间的等待,只不过它们侧重点不同:
CountDownLatch一般用于某个线程A等待若干个其他线程执行完任务之后,它才执行;
而CyclicBarrier一般用于一组线程互相等待至某个状态,然后这一组线程再同时执行;
另外,CountDownLatch是不能够重用的,而CyclicBarrier是可以重用的。
(2)Semaphore其实和锁有点类似,它一般用于控制对某组资源的访问权限。
2给出一些CountDownLatch使用的例子?
(1)开启多个线程分块下载一个大文件,每个线程只下载固定的一截,最后由另外一个线程来拼接所有的分段。
(2)应用程序的主线程希望在负责启动框架服务的线程已经启动所有的框架服务之后再执行。
(3)确保一个计算不会执行,直到所需要的资源被初始化。
3CountDownLatch 类中主要的方法?
public CountDownLatch(int count); //指定计数的次数,只能被设置1次
public void countDown(); //调用此方法则计数减1
public void await() throws InterruptedException //调用此方法会一直阻塞当前线程,直到计时器的值为0,除非线程被中断。
Public Long getCount(); //得到当前的计数
Public boolean await(long timeout, TimeUnit unit) //调用此方法会一直阻塞当前线程,直到计时器的值为0,除非线程被中断或者计数器超时,返回false代表计数器超时。