一、CountDownLatch同步工具
一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待
你可以向CounDownLatch对象设置一个初始计数器,任何在这个对象上调用wait()的方法都将阻塞,直到这个计数值达到0
。其他任务在结束其工作时,可以在该对象上调用countDown() 来减小这个计数值。 CountDownLatch被设计为只触发一次,计数值不能被重置。如果需要重置计数值。则可以使用CyclicBarrier
1.1 API
API | 描述 |
---|---|
CountDownLatch(int count) |
构造一个用给定计数初始化的 CountDownLatch。 |
void await() |
使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断 |
boolean await(long timeout, TimeUnit unit) |
使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断或超出了指定的等待时间 |
void countDown() |
递减锁存器的计数,如果计数到达零,则释放所有等待的线程;该方法并不会阻塞,只有await()调用会被阻塞,直到计数值达到0 |
long getCount() |
返回当前计数 |
1.2 案例分析
应用场景:
countDownLatch的典型用法是将一个程序分为n 个互相独立的可解决任务,并创建值为0的CountDownLatch。当每个任务完成时,都会在这个锁存器上调用countDown()。 等待问题被解决的任务在这个锁存器上调用 await(),将它们自己拦住,直至锁存器计数结束。
案例一:
package test;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
class TaskPortion implements Runnable {
private static int counter = 0;
private final int id = counter++;
private static Random random = new Random(47);
private final CountDownLatch countDownLatch;//同一个countDownLatch
public TaskPortion(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
try {
doWork();
countDownLatch.countDown();//完成对应的功能,coutDown()让计数值减一
} catch (InterruptedException e) {
}
}
public void doWork() throws InterruptedException {
TimeUnit.MILLISECONDS.sleep(random.nextInt(2000));
System.out.println(this + "completed");
}
public String toString() {
return String.format("%1$-3d ", id);
}
}
class WaitingTask implements Runnable {
private static int counter = 0;
private final int id = counter++;
private final CountDownLatch countDownLatch;
public WaitingTask(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
try {
//任何在这个对象上调用wait()的方法都将阻塞,直至这个计数值到达0
countDownLatch.await();
System.out.println("Latch barrier passed for " + this);
} catch (InterruptedException e) {
System.out.print(this + "interrupted");
}
}
public String toString() {
return String.format("WaitingTask %1$-3d", id);
}
}
public class CountDownLatchDemo {
static final int SIZE = 10;
public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newCachedThreadPool();
CountDownLatch countDownLatch = new CountDownLatch(SIZE);//计数值为100
for (int i = 0; i < 2; i++) { //10个等待
executorService.execute(new WaitingTask(countDownLatch));
}
for (int i = 0; i < SIZE; i++) { //100 任务
executorService.execute(new TaskPortion(countDownLatch));
}
System.out.println("Launched all tasks");
executorService.shutdown();
}
}
打印结果
Launched all tasks
9 completed
7 completed
5 completed
8 completed
1 completed
2 completed
6 completed
4 completed
0 completed
3 completed
Latch barrier passed for WaitingTask 0
Latch barrier passed for WaitingTask 1
TaskPortion
将随机地休眠一段时间,以模拟这部分工作的完成,而WaitingTask
表示系统中必须等待的部分,它要等待到问题的初始化部分完成为止。所有任务都使用了在main()
中定义的同一个单一的CountDownLatch
案例二
使用两个
CountDownLatch
的情况
public class CountdownLatchTest {
public static void main(String[] args) {
ExecutorService service = Executors.newCachedThreadPool();
final CountDownLatch cdOrder = new CountDownLatch(1);
final CountDownLatch cdAnswer = new CountDownLatch(3);
for(int i=0;i<3;i++){
Runnable runnable = new Runnable(){
public void run(){
try {
System.out.println("线程" + Thread.currentThread().getName() +
"正准备接受命令");
cdOrder.await();
System.out.println("线程" + Thread.currentThread().getName() +
"已接受命令");
Thread.sleep((long)(Math.random()*10000));
System.out.println("线程" + Thread.currentThread().getName() +
"回应命令处理结果");
cdAnswer.countDown();
} catch (Exception e) {
e.printStackTrace();
}
}
};
service.execute(runnable);
}
try {
Thread.sleep((long)(Math.random()*10000));
System.out.println("线程" + Thread.currentThread().getName() +
"即将发布命令");
cdOrder.countDown();
System.out.println("线程" + Thread.currentThread().getName() +
"已发送命令,正在等待结果");
cdAnswer.await();
System.out.println("线程" + Thread.currentThread().getName() +
"已收到所有响应结果");
} catch (Exception e) {
e.printStackTrace();
}
service.shutdown();
}
}
运行结果
线程pool-1-thread-1正准备接受命令
线程pool-1-thread-2正准备接受命令
线程pool-1-thread-3正准备接受命令
线程main即将发布命令
线程main已发送命令,正在等待结果
线程pool-1-thread-3已接受命令
线程pool-1-thread-1已接受命令
线程pool-1-thread-2已接受命令
线程pool-1-thread-2回应命令处理结果
线程pool-1-thread-3回应命令处理结果
线程pool-1-thread-1回应命令处理结果
线程main已收到所有响应结果
案例三
应用场景: 计算各个品牌的总库存。
/*
* CountDownLatch :闭锁,在完成某些运算是,只有其他所有线程的运算全部完成,当前运算才继续执行
*/
public class TestCountDownLatch {
public static void main(String[] args) {
final CountDownLatch latch = new CountDownLatch(50);
LatchDemo ld = new LatchDemo(latch);
long start = System.currentTimeMillis();
for (int i = 0; i < 50; i++) {
new Thread(ld).start();
}
try {
latch.await();
} catch (InterruptedException e) {
}
long end = System.currentTimeMillis();
System.out.println("耗费时间为:" + (end - start));
}
}
class LatchDemo implements Runnable {
private CountDownLatch latch;
public LatchDemo(CountDownLatch latch) {
this.latch = latch;
}
@Override
public void run() {
try {
for (int i = 0; i < 50000; i++) {
if (i % 2 == 0) {
System.out.println(i);
}
}
} finally {
latch.countDown();
}
}
}
1.4 用法补充
示例用法: 下面给出了两个类,其中一组 worker 线程使用了两个倒计数锁存器:
第一个类是一个启动信号,在 `driver` 为继续执行` worker` 做好准备之前,它会阻止所有的 `worker` 继续执行。
第二个类是一个完成信号,它允许 `driver` 在完成所有 `worker` 之前一直等待。
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() { ... }
}
另一种典型用法是,将一个问题分成 N 个部分,用执行每个部分并让锁存器倒计数的
Runnable
来描述每个部分,然后将所有 Runnable 加入到 Executor 队列。当所有的子部分完成后,协调线程就能够通过await
。(当线程必须用这种方法反复倒计数时,可改为使用 CyclicBarrier。)
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() { ... }
}
参考
- 张孝祥-Java多线程与并发库高级应用
- 《java编程思想》