看了各种资料和书,大家一致的意见都是CountDownLatch是计数器,只能使用一次,而CyclicBarrier的计数器提供reset功能,可以多次使用;但是我认为这只是一种比较笼统的区别,从javadoc中我们可以获取到这样描述:
CyclicBarrier:一种同步辅助工具,允许一组线程都等待彼此到达一个共同的障碍点。cyclicbarrier在涉及固定大小的线程组的程序中很有用,这些线程偶尔必须等待对方。这个屏障被称为(循环的),因为它可以在等待的线程被释放后重新使用。
CountDownLatch: 一种同步辅助工具,允许一个或多个线程等待一组在其他线程中执行的操作完成。倒计时锁存器用给定的计数初始化。由于调用倒计时方法,await方法阻塞直到当前计数达到零,之后释放所有等待线程,并立即返回任何后续的await调用。这是一种一次性现象——计数无法重置。如果需要重置计数的版本,请考虑使用CyclicBarrier。
从javadoc的定义中我们可以看出
CountDownLatch侧重于一个或者多个线程等待其他线程中执行的操作完成。应用场景如:背景想要获取用户最大的数据集合(用户基本数据、订单、购物车信息),但是用户基本数据、订单、购物车信息都在不同的微服务中,这种情况可以使用,主线程在获取基本数据的同时,将订单信息、购物信息另开线程异步获取,最终主线程会等待其他线程获取的消息进行合集返回
CyclicBarrier更加侧重:所有的线程都在await()的,等待最后一个计数之后,所有的线程统一执行,感觉可以用在压测上。
套路上来讲应该上一下demo测试一下:但是感觉依然没什么用。。。。。。
public class CountDownLatchTest {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(4);
for(int i = 0; i < latch.getCount(); i++){
new Thread( ()->{
Random rand = new Random();
int randomNum = rand.nextInt((3000 - 1000) + 1) + 1000;
try {
TimeUnit.MILLISECONDS.sleep(randomNum);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" CountDownLatch Test "+((double)randomNum/1000)+"s");
latch.countDown();
}, "CountDownLatch Test "+i).start();
}
System.out.println("CountDownLatch start");
latch.await();
System.out.println("CountDownLatch end");
}
}
public class CyclicBarrierTest {
public static void main(String[] args) {
CyclicBarrier barrier = new CyclicBarrier(3);
for(int i = 0; i < barrier.getParties(); i++){
new Thread(()->{
Random rand = new Random();
int randomNum = rand.nextInt((3000 - 1000) + 1) + 1000;
try {
TimeUnit.MILLISECONDS.sleep(randomNum);
System.out.println(Thread.currentThread().getName() + ", CyclicBarrier Test "+((double)randomNum/1000)+"s");
barrier.await();
} catch (Exception e) {
e.printStackTrace();
}
}, "队友"+i).start();
}
System.out.println("CyclicBarrier Test is finished.");
}
}
CyclicBarrier源码
来了解一下CyclicBarrier的套路:源码一路了然
private int dowait(boolean timed, long nanos) throws InterruptedException, BrokenBarrierException, TimeoutException {
//使用了ReentrantLock锁
final ReentrantLock lock = this.lock;
lock.lock();
try {
final CyclicBarrier.Generation g = generation;
if (g.broken)
throw new BrokenBarrierException();
if (Thread.interrupted()) {
breakBarrier();
throw new InterruptedException();
}
//count值为new CyclicBarrier(3)中设置的3 可以看一下他的构造方法
int index = --count;
//如果index减到0 我们开始执行下面的代码 从构造方法中可以知道 barrierCommand为null
//所以直接执行nextGeneration(); 他的核心代码就是trip.signalAll(); 唤醒所有的线程
//其中trip为 Condition trip = lock.newCondition();
if (index == 0) { // tripped
boolean ranAction = false;
try {
final Runnable command = barrierCommand;
if (command != null)
command.run();
ranAction = true;
nextGeneration();
return 0;
} finally {
if (!ranAction)
breakBarrier();
}
}
// 如果index不为0 trip.await(); 等待唤醒
for (;;) {
try {
if (!timed)
trip.await();
else if (nanos > 0L)
nanos = trip.awaitNanos(nanos);
} catch (InterruptedException ie) {
if (g == generation && ! g.broken) {
breakBarrier();
throw ie;
} else {
Thread.currentThread().interrupt();
}
}
}
} finally {
lock.unlock();
}
}
总结一下就是:如果CyclicBarrier中的index没有减到0就在await(),减到0是调用signalAll()唤醒所有线程执行任务;
CountDownLatch的源码
下面的源码可以知道最中就是调用AQS获取status值
从CountDownLatch和CyclicBarrier的源码中可以知道,他们两个的实现方式不一样,一个是利用AQS获取status值,另一种是通过ReentrantLock的唤醒机制来实现。
CountDownLatch来说,重点是“一个线程等待(多个线程)”,而其他的N个线程在完成“某件事情”之后,可以终止,也可以等待。而对于CyclicBarrier,重点是多个线程,在任意一个线程没有完成,所有的线程都必须互相等待,然后继续一起执行