CyclicBarrier循环屏障
目录
CyclicBarrier和CountDownLatch区别
CyclicBarrier原理
下面是部分源码,以及源码中调用相关类的部分源码剖析:
public class CyclicBarrier {
private final ReentrantLock lock = new ReentrantLock();
private final Condition trip = lock.newCondition();
private Generation generation = new Generation();
private int count;
public CyclicBarrier(int parties) {
this.parties = parties;
this.count = parties;
}
private void nextGeneration() {
trip.signalAll();
count = parties;
generation = new Generation();
}
private void breakBarrier() {
generation.broken = true;
count = parties;
trip.signalAll();
}
/**
* Main barrier code, covering the various policies.
*/
private int dowait(boolean timed, long nanos) {
lock.lock();
try {
final Generation g = generation;
int index = --count;
if (index == 0) { // 屏障开启,可通行
boolean ranAction = false;
try {
ranAction = true;
nextGeneration(); // 唤醒所有卡在barrier屏障前的线程,并恢复count使其可循环利用
return 0;
} finally {
if (!ranAction)
breakBarrier();
}
}
// loop until tripped, broken, interrupted, or timed out
for (;;) { // index!=0 说明人还没到齐,仍然无法开启屏障
try {
if (!timed)
trip.await(); // 当前线程压入条件队列
} catch (InterruptedException ie) {
}
if (g != generation)
return index;
}
} finally {
lock.unlock();
}
}
public int await() throws InterruptedException, BrokenBarrierException {
try {
return dowait(false, 0L);
} catch (TimeoutException toe) {
}
}
}
public class ConditionObject implements Condition, java.io.Serializable {
public ConditionObject() { }
private void doSignalAll(Node first) {
lastWaiter = firstWaiter = null;
do { //从第一个节点开始,循环处理,唤醒所有barrier屏障前的等待线程
Node next = first.nextWaiter;
first.nextWaiter = null;
transferForSignal(first);
first = next;
} while (first != null);
}
public final void signalAll() {
doSignalAll(first);
}
}
public abstract class AbstractQueuedSynchronizer{
final boolean transferForSignal(Node node) {
LockSupport.unpark(node.thread); // 唤醒线程
return true;
}
}
源代码分析如下:
CyclicBarrier类的await()方法:是我们程序中经常调用的,其内部调用了Cyclic类的dowait()方法
CyclicBarrier类的dowait()方法:
获得锁:
临界区:业务逻辑,判断当前屏障能否放行
index==0,能放行,则调用CyclicBarrier类的nextCondition()方法
index!=0, 不能放行,当前线程压入条件队列,进行等待
释放锁:
CyclicBarrier类的nextCondition()方法:调用ConditionObject类的signalAll()方法,恢复循环计数功能count = parties;
ConditionObject类的signalAll()方法:循环处理,条件队列中的线程转入同步队列准备执行。即唤醒所有到达屏障前的等待线程
源码分析总结:
白话文阐述:众人都到达,才能推开屏障大门,得以通行。假定大门4斤,每人只能推动1斤,必须4人都到齐后,才能推开大门。4人通过大门后,大门自行关闭又形成屏障,需要下一组还得是4个人都到门前一起才能推开大门,如此循环反复....直至所有人都通过大门。
程序上来说:屏障计数量初始化为N。每一个线程到达屏障前(即当前线程运行到await()方法处),该线程被阻塞,屏障计数量N-1,直至屏障降为零(即到达屏障前的线程个数之和达到屏障计数量N),屏障才会打开,唤醒这N个到达屏障前的多线程,都可通过屏障,继续得以执行。
适应的业务场景:
两个或者多个线程的一组线程,必须在预定的执行点进行等待,直到这组线程中的所有线程都到达执行点为止。举例:景区观光小车,人不坐满不发车。在车子想开走观光之前(执行点),一车人(一组线程)必须坐满,才启动车辆。
CyclicBarrier示例
import java.util.concurrent.*;
import java.util.concurrent.locks.*;
import java.util.concurrent.atomic.*;
/**
* 任务
*/
class PlayTask implements Runnable
{
private CyclicBarrier cb;
public PlayTask(CyclicBarrier cb){
this.cb = cb;
}
public void run(){
try{
long sleepTime = (long)(Math.random() * 2500) ;
String tName = Thread.currentThread().getName();
System.out.println(tName+":在做事,需要耗时["+sleepTime+"]");
Thread.sleep(sleepTime);
cb.await();
System.out.println("凑够"+cb.getParties()+",["+tName+"]已上车,出发!");
}
catch(InterruptedException | BrokenBarrierException e){
}
finally{
}
}
}
/**
* 测试
*/
public class CyclicBarrierTest
{
public static void main(String[] args)
{
int count = 3 ; //计数量
CyclicBarrier cb = new CyclicBarrier(count,new Runnable() {
public void run(){
System.out.println("\n\r有"+count+"个线程到达...");
}
});
System.out.println("景区,请排队上车观光,每"+count+"人一组上车...\n\r");
PlayTask eTask = new PlayTask(cb); //任务:凑够3人去观光
Thread[] threads = new Thread[count*3]; //多线程,每count个一组
for (int i = 0 ; i < count*3 ; ++i) {
threads[i] = new Thread(eTask,"T"+i);
threads[i].start();
}
}
}
程序结果如下:
景区,请排队上车观光,每3人一组上车...
T5:在做事,需要耗时[1717]
T3:在做事,需要耗时[2117]
T8:在做事,需要耗时[1261]
T1:在做事,需要耗时[286]
T0:在做事,需要耗时[173]
T4:在做事,需要耗时[221]
T6:在做事,需要耗时[2319]
T7:在做事,需要耗时[1538]
T2:在做事,需要耗时[1259]
有3个线程到达...
凑够3,[T1]已上车,出发!
凑够3,[T4]已上车,出发!
凑够3,[T0]已上车,出发!
有3个线程到达...
凑够3,[T7]已上车,出发!
凑够3,[T8]已上车,出发!
凑够3,[T2]已上车,出发!
有3个线程到达...
凑够3,[T6]已上车,出发!
凑够3,[T3]已上车,出发!
凑够3,[T5]已上车,出发!
CyclicBarrier和CountDownLatch区别
1。
CountDownLatch:主线程等在这里,直到N个子线程到达后,主线程才得以继续往下执行;
CyclicBarrier:则是N个子线程构成一组,组内成员有快有慢,快的成员到达屏障后,无法通过,必须组内的所有成员都忙好到达了屏障,屏障才会打开放行这一组内的N个子线程,使得这N个子线程得以通过屏障继续执行,与主线程没关系,是一组内的N个子线程等待彼此都到达屏障。如果非要说和CountDownLatch类似的地方就是,构造函数public CyclicBarrier(int parties, Runnable barrierAction)这样的,这里的Runnable就是N个子线程都到达了之后,才能得以执行。
2。
CountDownLatch计数为0后,无法重置,一次性的;
CyclicBarrier计数达到后,自动重置,可循环利用的。