上篇 JDK1.8并发包之--Semaphore 发现一个方法,搞懂Jdk的类注释,就能快速了解该类的用法,于是从CountDownLatch的英文注释出发,GO!
1、第一段,实现目标
A synchronization aid that allows one or more threads to wait until
a set of operations being performed in other threads completes.
一句话告诉我们CountDownLatch的目标是,等待一组线程执行完成。场景如下,10个线程并发执行一系列任务,肯定有的线程先跑完,那么先跑完的就等省下没跑完的线程执行完毕,再执行下面任务。
2、第二段,运行机制
A {@code CountDownLatch} is initialized with a given count.
The {@link #await await} methods block until the current count reaches
zero due to invocations of the {@link #countDown} method, after which
all waiting threads are released and any subsequent invocations of
{@link #await await} return immediately. This is a one-shot phenomenon
-- the count cannot be reset. If you need a version that resets the
count, consider using a {@link CyclicBarrier}.
CountDownLatch初始化构造传递整形count参数,await方法直到count降为0才会立即返回,调用countDown方法使count减一,count等于0后,所有的等待线程被唤醒。这个过程中count不能被重置,如果要重置count,考虑使用CycliBarrier。
3、第三段,用途
A {@code CountDownLatch} is a versatile synchronization tool
and can be used for a number of purposes. A
{@code CountDownLatch} initialized with a count of one serves as a
simple on/off latch, or gate: all threads invoking {@link #await await}
wait at the gate until it is opened by a thread invoking {@link
#countDown}. A {@code CountDownLatch} initialized to N
can be used to make one thread wait until N threads have
completed some action, or some action has been completed N times.
CountDownLatch有种用途,可以用来作为门栓或者门岗,所有的线程调用await方法阻塞,直到一个线程调用countDown减1,CountDownLatch初始化为N,保证一个线程等待N个线程完成动作后,或者执行N次后,再接着执行。
4、第四段
A useful property of a {@code CountDownLatch} is that it
doesn't require that threads calling {@code countDown} wait for
the count to reach zero before proceeding, it simply prevents any
thread from proceeding past an {@link #await await} until all
threads could pass.
CountDownLatch有个非常有用的特性就是调用countDown减1不用等待减到0 。仅仅是避免其他线程都执行完了才能通过await方法。
5、第五段 案例
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.
说的两处使用CountDownLatch的场景,第一个CountDownLatch避免worker执行任务,直到driver准备好了,worker才开始执行。第二个CountDownLatch是信号量,让driver等待worker执行完毕。
@Slf4j
public class Driver {
private static final int N = 10;
public static void main(String[] args) throws InterruptedException {
CountDownLatch startSignal = new CountDownLatch(1);
CountDownLatch doneSignal = new CountDownLatch(N);
for (int i = 0; i < N; ++i) {
new Thread(new Worker(startSignal, doneSignal, "Thread-" + i)).start();
}
doSomethingElse();
startSignal.countDown();
doSomethingElse();
doneSignal.await();
}
static void doSomethingElse() throws InterruptedException {
TimeUnit.SECONDS.sleep(2);
log.info("线程:{} 睡眠2秒", Thread.currentThread().getName());
}
static class Worker extends Thread {
private final CountDownLatch startSignal;
private final CountDownLatch doneSignal;
public Worker(CountDownLatch startSignal, CountDownLatch doneSignal, String name) {
this.startSignal = startSignal;
this.doneSignal = doneSignal;
this.setName(name);
}
@Override
public void run() {
try {
startSignal.await();
doWork();
doneSignal.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
void doWork() throws InterruptedException {
TimeUnit.SECONDS.sleep(3);
log.info("线程:{} 睡眠3秒", this.getName());
}
}
}
还有另外一种用法是,把一个问题拆分成N个部分,每个部分都是一个线程任务,然后执行countDwon直到为0,后续的请求放入队列中缓存起来。当N个部分全部执行完成,协调线程就会通过await,进一步执行任务。另外,如果线程需要重复执行countDown,就要用CyclicBarrier。
Another typical usage would be to divide a problem into N parts,
* describe each part with a Runnable that executes that portion and
* counts down on the latch, and queue all the Runnables to an
* Executor. When all sub-parts are complete, the coordinating thread
* will be able to pass through await. (When threads must repeatedly
* count down in this way, instead use a {@link CyclicBarrier}.)
@Slf4j
public class CountDownLatchDemo2 {
private static final int N = 10;
public static void main(String[] args) throws InterruptedException {
CountDownLatch doneSignal = new CountDownLatch(N);
Executor e = Executors.newCachedThreadPool();
for (int i = 0; i < N; ++i) {
e.execute(new Worker(doneSignal, i));
}
System.out.println(doneSignal.getCount());
doneSignal.await();
System.out.println(doneSignal.getCount());
doneSignal = new CountDownLatch(N);
for (int i = N; i < N + N; ++i) {
e.execute(new Worker(doneSignal, i));
}
System.out.println(doneSignal.getCount());
}
static class Worker extends Thread {
private final CountDownLatch doneSignal;
private final int i;
Worker(CountDownLatch doneSignal, int i) {
this.doneSignal = doneSignal;
this.i = i;
}
@Override
public void run() {
try {
doWork(i);
doneSignal.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
void doWork(int i) throws InterruptedException {
TimeUnit.SECONDS.sleep(i);
log.info("线程:{} 等待{}秒", this.getName(), i);
}
}
}
总结,CountDownLatch有两种用法,第一、协调任务执行关卡,如第一个例子那样,Driver-Worker的关系,好比司机-搬运工,搬运工必须等司机把车开过来才能工作,司机把车开到后,必须等搬运工把所有东西都装完才能把车开到目的地。第二、分别大任务,如第二个例子,把问题分解成N个小份,再分配给N个线程执行,等N个线程执行完毕后再分配下一批任务。笔者用第二种方式同步数据,效率尤其快。