CyclicBarrier这个并发工具类和上篇文章中提到的CountDownLatch比较类似,可以把CyclicBarrier看做是可以可以复用的CountDownLatch。
CountDownLatch在Oracle官网文档中定义是,一组线程其他另外一组线程都执行完成之后,该组线程才可以同时进行工作。这和软件开发中协作非常类似,比如dev团队开发完成之后,qa团队才可以进行测试工作。qa团队是一组等待线程,dev团队是一组工作的线程,只有当dev全部完成之后,qa才开始进行测试。
CyclicBarrier在官网的定义是,一组线程中,必须相互等待其他的线程完成之后,他们才可以进入下一个协作项目反复如此。比如还是在软件开发中,一个项目来了,需要同时由后端团队和前端团队都开发完成之后才能提测,否则就相互等待,直到它们全部完成之后,才可以一起进入一个循环中。
我们来看一个非常简单的CyclicBarrier例子:
CyclicBarrier cyclicBarrier=new CyclicBarrier(2, new Runnable() {
@Override
public void run() {
System.out.println("任务完成,触发一次.....");
}
});
Runnable runnable=new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
};
new Thread(runnable).start();
new Thread(runnable).start();
new Thread(runnable).start();
new Thread(runnable).start();
输出结果:
任务完成,触发一次.....
任务完成,触发一次.....
CountDownLatch和CyclicBarrier的不同之处在于:
(1)CountDownLatch仅仅可以使用一次而CyclicBarrier可以循环利用
(2)CountDownLatch类强调的是调用countDown方法的次数,而CyclicBarrier类强调的是至少有N个线程调用await方法。
(3)CyclicBarrier方法可以额外注册一个任务,在每轮循环执行await之后,会由最后一个调用await方法的线程,负责调用额外的任务。如上面的例子。
(4)CountDownLatch的底层使用的AQS的共享锁来实现的,而CyclicBarrier则是由ReentrantLock+Condition实现的相对来说更简单。
关于两者的功能区别,可以看下面两个伪代码:
CountDonwLatch:
public class CountDownLatch {
private Object mutex = new Object();
private int count;
public CountDownLatch(int count) {
this.count = count;
}
public void await() throws InterruptedException {
synchronized (mutex) {
while (count > 0) {
mutex.wait();
}
}
}
public void countDown() {
synchronized (mutex) {
if (--count == 0)
mutex.notifyAll();
}
}
}
CyclicBarrier:
public class CyclicBarrier {
private Object mutex = new Object();
private int count;
public CyclicBarrier(int count) {
this.count = count;
}
public void await() throws InterruptedException {
synchronized (mutex) {
count--;
while(count > 0)
mutex.wait();
mutex.notifyAll();
}
}
}
总结:
本文主要了介绍了Java里面CyclicBarrier类功能以及它与CountDownLatch的区别和联系,最后并给出了实现他们的一些伪代码,两者的主要不同之处在于CyclicBarrier是可以多次复用的一个示例,只要指定的数量的线程调用await方法后,reset重置方法就会自动调用,不需要在代码中显式使用,这一点需要注意。