CountDownLatch
直译过来就是倒计数(CountDown)门闩(Latch)。倒计数不用说,门闩的意思顾名思义就是阻止前进。在这里就是指 CountDownLatch.await() 方法在倒计数为0之前会阻塞当前线程。当计数器count变为0时,所有线程不再等待,同时运行。
CountDownLatch 的作用和 Thread.join() 方法类似,可用于一组线程和另外一组线程的协作。例如,主线程在做一项工作之前需要一系列的准备工作,只有这些准备工作都完成,主线程才能继续它的工作。这些准备工作彼此独立,所以可以并发执行以提高速度。在这个场景下就可以使用 CountDownLatch 协调线程之间的调度了。
上面说的有点儿抽象,还是用实际例子说明吧。。。
下面模拟的是一个短跑比赛,共3名运动员,那么比赛要开始的话,就必须等到所有的运动员准备完毕。
下面看代码:
<span style="font-size:18px;">package test;
import java.io.IOException;
import java.util.Random;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Test4 {
public static void main(String[] args) throws IOException, InterruptedException {
//如果将参数改为4,但是下面只加入了3个选手,这永远等待下去
CountDownLatch begin = new CountDownLatch(3);
ExecutorService executor = Executors.newFixedThreadPool(3);
executor.submit(new Thread(new Runner(begin, "1号选手")));
executor.submit(new Thread(new Runner(begin, "2号选手")));
executor.submit(new Thread(new Runner(begin, "3号选手")));
executor.shutdown();
}
}
class Runner implements Runnable {
// 当begin为0时,运动员起跑
private CountDownLatch begin;
private String name;
public Runner(CountDownLatch begin, String name) {
super();
this.begin = begin;
this.name = name;
}
@Override
public void run() {
try {
Thread.sleep(1000 * (new Random()).nextInt(8));
System.out.println(name + " 准备好了...");
//count减一,当count为0时,等待结束,所有运动员准备完毕,比赛开始
begin.countDown();
//等待所有运动员准备好
begin.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + " 起跑!");
try {
Thread.sleep((long)(Math.random()*1000));
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(name + " end!");
}
} </span>
输出结果:
2号选手 准备好了...
1号选手 准备好了...
3号选手 准备好了...
3号选手 起跑!
2号选手 起跑!
1号选手 起跑!
3号选手 end!
1号选手 end!
2号选手 end!
当 begin.await() 会阻塞线程,当 begin.countDown()被调用之后,计数器count(为什么是count?看源码)减一,当count为0时,所有线程停止等待。
说完了CountDownLatch,再来看看CyclicBarrier。。。。。。
CyclicBarrier
CyclicBarrier 翻译过来叫循环障碍。它主要的方法就是一个:await()。await() 方法没被调用一次,计数便会减少1,并阻塞住当前线程。当计数减至0时,阻塞解除,所有在此 CyclicBarrier 上面阻塞的线程开始运行。在这之后,如果再次调用 await() 方法,计数就又会变成 N-1,新一轮重新开始,这便是 Cyclic 的含义所在。
CyclicBarrier 的使用并不难,但需要主要它所相关的异常。除了常见的异常,CyclicBarrier.await() 方法会抛出一个独有的 BrokenBarrierException。这个异常发生在当某个线程在等待本 CyclicBarrier 时被中断或超时或被重置时,其它同样在这个CyclicBarrier 上等待的线程便会收到 BrokenBarrierException而停止等待。
还是上面的例子,下面我们用CyclicBarrier实现一下:
package test;
import java.io.IOException;
import java.util.Random;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Test3 {
public static void main(String[] args) throws IOException, InterruptedException {
//如果将参数改为4,但是下面只加入了3个选手,这永远等待下去
//Waits until all parties have invoked await on this barrier.
CyclicBarrier barrier = new CyclicBarrier(3);
ExecutorService executor = Executors.newFixedThreadPool(3);
executor.submit(new Thread(new Runner(barrier, "1号选手")));
executor.submit(new Thread(new Runner(barrier, "2号选手")));
executor.submit(new Thread(new Runner(barrier, "3号选手")));
executor.shutdown();
}
}
class Runner implements Runnable {
// 当barrier为0时,运动员起跑
private CyclicBarrier barrier;
private String name;
public Runner(CyclicBarrier barrier, String name) {
super();
this.barrier = barrier;
this.name = name;
}
@Override
public void run() {
try {
Thread.sleep(1000 * (new Random()).nextInt(8));
System.out.println(name + " 准备好了...");
// barrier的await方法,在所有参与者都已经在此 barrier 上调用 await 方法之前,将一直等待。
barrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println(name + " 起跑!");
try {
Thread.sleep((long)(Math.random()*1000));
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(name + " end!");
}
}
输出结果:
1号选手 准备好了...
2号选手 准备好了...
3号选手 准备好了...
3号选手 起跑!
1号选手 起跑!
2号选手 起跑!
2号选手 end!
3号选手 end!
1号选手 end!
功能和结果并没有什么不同
CyclicBarrier 和 CountDownLatch 在用法上的不同
CountDownLatch 适用于一组线程和另一个主线程之间的工作协作。一个主线程等待一组工作线程的任务完毕才继续它的执行是使用 CountDownLatch 的主要场景;CyclicBarrier 用于一组或几组线程,还是以刚才短跑比赛为例,刚才只是一组选手比赛,如果我们改成两组呢???这时有两组选手,第一组全部跑完,第二组才能开始。这时候使用CyclicBarrier实现就很容易,因为CyclicBarrier每次count归零以后都会重置,所以可以循环使用,而CountDownLatch却不具备这个功能。
下面看例子:
package test;
import java.io.IOException;
import java.util.Random;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Test5 {
public static void main(String[] args) throws IOException, InterruptedException {
//如果将参数改为4,但是下面只加入了3个选手,这永远等待下去
//Waits until all parties have invoked await on this barrier.
CyclicBarrier barrier = new CyclicBarrier(3);
ExecutorService executor = Executors.newFixedThreadPool(3);
executor.submit(new Thread(new Runner(barrier, "第1组:1号选手")));
executor.submit(new Thread(new Runner(barrier, "第1组:2号选手")));
executor.submit(new Thread(new Runner(barrier, "第1组:3号选手")));
executor.submit(new Thread(new Runner(barrier, "第2组:1号选手")));
executor.submit(new Thread(new Runner(barrier, "第2组:2号选手")));
executor.submit(new Thread(new Runner(barrier, "第2组:3号选手")));
executor.shutdown();
}
}
class Runner implements Runnable {
// 一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)
private CyclicBarrier barrier;
private String name;
public Runner(CyclicBarrier barrier, String name) {
super();
this.barrier = barrier;
this.name = name;
}
@Override
public void run() {
try {
Thread.sleep(1000 * (new Random()).nextInt(8));
System.out.println(name + " 准备好了...");
// barrier的await方法,在所有参与者都已经在此 barrier 上调用 await 方法之前,将一直等待。
barrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println(name + " 起跑!");
try {
Thread.sleep((long)(Math.random()*1000));
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(name + " end!");
}
}
输出结果:
第1组:3号选手 准备好了...
第1组:1号选手 准备好了...
第1组:2号选手 准备好了...
第1组:2号选手 起跑!
第1组:3号选手 起跑!
第1组:1号选手 起跑!
第1组:1号选手 end!
第1组:3号选手 end!
第1组:2号选手 end!
第2组:1号选手 准备好了...
第2组:2号选手 准备好了...
第2组:3号选手 准备好了...
第2组:3号选手 起跑!
第2组:2号选手 起跑!
第2组:1号选手 起跑!
第2组:3号选手 end!
第2组:2号选手 end!
第2组:1号选手 end!
这确实使我们想要的结果。
那么使用CyclicBarrier会产生什么样的结果呢???
试验一下:
import java.io.IOException;
import java.util.Random;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Test6 {
public static void main(String[] args) throws IOException, InterruptedException {
//如果将参数改为4,但是下面只加入了3个选手,这永远等待下去
CountDownLatch begin = new CountDownLatch(3);
ExecutorService executor = Executors.newFixedThreadPool(3);
executor.submit(new Thread(new Runner(begin, "第1棒:1号选手")));
executor.submit(new Thread(new Runner(begin, "第1棒:2号选手")));
executor.submit(new Thread(new Runner(begin, "第1棒:3号选手")));
executor.submit(new Thread(new Runner(begin, "第2棒:1号选手")));
executor.submit(new Thread(new Runner(begin, "第2棒:2号选手")));
executor.submit(new Thread(new Runner(begin, "第2棒:3号选手")));
executor.shutdown();
}
}
class Runner implements Runnable {
// 当begin为0时,运动员起跑
private CountDownLatch begin;
private String name;
public Runner(CountDownLatch begin, String name) {
super();
this.begin = begin;
this.name = name;
}
@Override
public void run() {
try {
Thread.sleep(1000 * (new Random()).nextInt(8));
System.out.println(name + " 准备好了...");
//count减一,当count为0时,等待结束,所有运动员准备完毕,比赛开始
begin.countDown();
//等待所有运动员准备好
begin.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + " 起跑!");
try {
Thread.sleep((long)(Math.random()*1000));
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(name + " end!");
}
}
输出结果:
第1组:2号选手 准备好了...
第1组:1号选手 准备好了...
第1组:3号选手 准备好了...
第1组:3号选手 起跑!
第1组:2号选手 起跑!
第1组:1号选手 起跑!
第1组:1号选手 end!
第1组:3号选手 end!
第1组:2号选手 end!
第2组:3号选手 准备好了...
第2组:3号选手 起跑!
第2组:3号选手 end!
第2组:1号选手 准备好了...
第2组:1号选手 起跑!
第2组:1号选手 end!
第2组:2号选手 准备好了...
第2组:2号选手 起跑!
第2组:2号选手 end!
因为count归零后一直保持为0,不可复用,所以使用一个CountDownLatch无法实现上述功能。
有没有发现CyclicBarrier这个名字真是起绝了,一个cycle....一切尽在不言中.....