什么是CountDownLatch
CountDownLatch这个类在多线程场景中已经用过跟多次了,一直没有认真总结下。今天来对这个类做个总结,算是重新学习下。
CountDownLatch类在java.util.concurrent包中,诞生于JDK1.5版本,是一个同步辅助工具。为了彻底了解它,我们来看下源码。
/**
* CountDownLatch是一个同步辅助工具,允许一个或更多线程等待直至
* 其他线程的一些列操作完成。
* A synchronization aid that allows one or more threads to wait until
* a set of operations being performed in other threads completes.
*
* @since 1.5
* @author Doug Lea
*/
public class CountDownLatch {
/**
* CountDownLatch的同步控制依赖于AQS
* 使用AbstractQueuedSynchronizer的状态代表计数
* Synchronization control For CountDownLatch.
* Uses AQS state to represent count.
*/
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
/**
* 构造方法,根据计数器的初始值初始化状态
* 实际上调用了父类AQS的setState(int newState)
*/
Sync(int count) {
setState(count);
}
/**
* 获取计数器的值
* 实际上调用了父类的AQS的getState()
*/
int getCount() {
return getState();
}
/**
* 重写父类的tryAcquireShared(int acquires)
* 尝试在共享模式下获取状态,该方法应该查询对象的状态是否允许它在共享模式下获取状态,如果允许就获取状态。
*/
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
/**
* 重写父类的tryReleaseShared(int releases)
* 在共享模式下尝试使状态表现为释放,该方法总是由执行释放的线程调用。
*/
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; //做减1操作
//调用了父类的compareAndSetState(int expect, int update)更新状态,其中用到了Java中的CAS理论
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}
private final Sync sync;
/**
* 根据给定的值初始化一个CountDownLatch对象
* Constructs a {@code CountDownLatch} initialized with the given count.
*
*/
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count); //调用Sync的构造方法
}
/**
* 使当前线程等待直至计数器的数值倒计为0, 除非当前线程被中断。
* Causes the current thread to wait until the latch has counted down to
* zero, unless the thread is {@linkplain Thread#interrupt interrupted}.
*
*/
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
/**
* 使当前线程等待直至计数器的数值倒计为0, 除非当前线程被中断或等待至指定的时间。
*
* Causes the current thread to wait until the latch has counted down to
* zero, unless the thread is {@linkplain Thread#interrupt interrupted},
* or the specified waiting time elapses.
*/
public boolean await(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
/**
* 计数器的数值减1,计数器的数值为0会释放(唤醒)所有等待的线程。
* Decrements the count of the latch, releasing all waiting threads if
* the count reaches zero.
*/
public void countDown() {
sync.releaseShared(1);
}
/**
* 获取计数器的当前数值
* Returns the current count.
*/
public long getCount() {
return sync.getCount();
}
public String toString() {
return super.toString() + "[Count = " + sync.getCount() + "]";
}
}
通过阅读CountDownLatch的源码,我们对其有了一个初步的认识。源码下的定义翻译过来为:CountDownLatch是一个同步辅助工具,允许一个或更多线程等待直至其他线程的一些列操作完成。
源码下的定义:A synchronization aid that allows one or more threads to wait until a set of operations being performed in other threads completes.
在实际的应用场景中,对于多线程的同步,CountDownLatch起到的是一个计数器的作用。CountDownLatch的构造方法传入了计数器的初始值,初始值要大于0,否则,会抛出IllegalArgumentException(“count < 0”)。在计数器的值递减为0之前,使用了await()的线程会一直等待,除非线程被中断。为什么说是递减呢?在源码中,countDown()对计数器值的影响就是减1。每当有线程调用一次countDown(),计数器的数值就会减1。
另外,通过源码我们可以看出,计数器的初始值在执行CountDownLatch(int count)指定,没有提供更改计数器值的方法。也就是说,计数器的值在递减为0之后,这个计数器也就失效了。
什么场景下用CountDownLatch呢
对于CountDownLatch我们也了解比较多了,那么什么场景下用CountDownLatch呢?在 CountDownLatch的类注释上已经给了简单说明,如下:
Sample usage: Here is a pair of classes in which a group
of worker threads use two countdown latches:
- The first is a start signal that prevents any worker from proceeding
until the driver is ready for them to proceed;
- The second is a completion signal that allows the driver to wait
until all workers have completed.
从编程的角度来看,就是以下两个场景:
- 多个工作线程会等待发号施令的线程发出开始的命令才会并行执行任务。
- 发号施令的线程等多个工作线程的任务执行完毕后才会发出完成的命令。
现在,我们通过代码来模拟这两种场景:
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author zhaoheng
* @date 2018年8月16日
*
*/
public class CountDownLatchDemo1 implements Runnable{
private static final int NUM = 10;
//准备计数器
private static final CountDownLatch readyCountDown = new CountDownLatch(NUM);
//开始计数器
private static final CountDownLatch beginCountDown = new CountDownLatch(1);
//完成计数器
private static final CountDownLatch completeCountDown = new CountDownLatch(NUM);
private static final CountDownLatchDemo1 demo = new CountDownLatchDemo1();
@Override
public void run() {
try {
System.out.println("工作线程" + Thread.currentThread().getId() + ":准备完成,等待开始");
readyCountDown.countDown();
beginCountDown.await(); //等待开始命令
System.out.println("工作线程" + Thread.currentThread().getId() + ":收到开始命令,开始执行");
Thread.sleep(new Random().nextInt(10)*1000);
System.out.println("工作线程" + Thread.currentThread().getId() + ":完成工作");
completeCountDown.countDown();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(10);
for (int i=0;i<NUM;i++) {
executorService.submit(demo);
}
try {
readyCountDown.await(); //等所有线程准备完成
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发号施令线程 --- 所有线程已做好准备...");
System.out.println("发号施令线程 --- 发出开始命令...");
beginCountDown.countDown();
try {
completeCountDown.await(); //等所有线程完成工作
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发号施令线程 --- 所有线程完成工作...");
executorService.shutdown();
}
}
在模拟的场景中,分别定义了准备、开始、完成三个计数器,负责发号施令的主线程和负责具体工作的10个线程池子线程。以上测试代码的逻辑如下:
- 发号施令的主线程等10个子线程都准备完毕后发出开始命令。
- 10个子线程接收到开始命令后,并行执行任务。
- 发号施令的主线程等10个子线程都完成工作后发出完成命令。
测试代码,运行结果符合我们代码的逻辑。
工作线程11:准备完成,等待开始
工作线程14:准备完成,等待开始
工作线程12:准备完成,等待开始
工作线程10:准备完成,等待开始
工作线程13:准备完成,等待开始
工作线程15:准备完成,等待开始
工作线程18:准备完成,等待开始
工作线程16:准备完成,等待开始
工作线程19:准备完成,等待开始
工作线程17:准备完成,等待开始
发号施令线程 --- 所有线程已做好准备...
发号施令线程 --- 发出开始命令...
工作线程11:收到开始命令,开始执行
工作线程15:收到开始命令,开始执行
工作线程13:收到开始命令,开始执行
工作线程10:收到开始命令,开始执行
工作线程12:收到开始命令,开始执行
工作线程14:收到开始命令,开始执行
工作线程17:收到开始命令,开始执行
工作线程19:收到开始命令,开始执行
工作线程16:收到开始命令,开始执行
工作线程18:收到开始命令,开始执行
工作线程17:完成工作
工作线程19:完成工作
工作线程16:完成工作
工作线程10:完成工作
工作线程18:完成工作
工作线程14:完成工作
工作线程11:完成工作
工作线程13:完成工作
工作线程15:完成工作
工作线程12:完成工作
发号施令线程 --- 所有线程完成工作...
至此,对CountDownLatch的介绍完毕。在看CountDownLatch的源码时,知道了CountDownLatch的同步控制依赖于AQS(AbstractQueuedSynchronizer),本篇文章没有对AQS做进一步的解读。如果对AQS感兴趣的,可以阅读AQS的源码。
由于笔主水平有限,笔误或者不当之处还请批评指正。