JAVA同时启动多个线程(CyclicBarrier实现及原理分析)一文中提到可以使用CountDownLatch来实现多个线程的同时启动,本文讲讲使用CyclicBarrier的实现方式以及CyclicBarrier的复用。
public class CyclieBarrierTest {
public static void main(String[] args) {
int n = 5;
//定义cyclicBarrier,第一个参数是计数器的大小,第二个参数是计数器为0时需要执行的任务
CyclicBarrier cyclicBarrier = new CyclicBarrier(n, new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " 唤醒时间:" + System.currentTimeMillis());
}
});
for (int i = 0; i < n; i++) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
//阻塞线程等待所有线程就绪
cyclicBarrier.await();
System.out.println(Thread.currentThread().getName() + " 开始时间:" + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
});
thread.setName("线程-" + i);
thread.start();
System.out.println(thread.getName() + " 就绪时间:" + System.currentTimeMillis());
}
}
}
运行结果如下: 可以看出,使用CyclicBarrier 同样可以实现同时启动多个线程
线程-0 就绪时间:1609232968396
线程-1 就绪时间:1609232968396
线程-2 就绪时间:1609232968396
线程-3 就绪时间:1609232968396
线程-4 就绪时间:1609232968396
线程-4 唤醒时间:1609232968397
线程-4 开始时间:1609232968397
线程-0 开始时间:1609232968397
线程-2 开始时间:1609232968397
线程-1 开始时间:1609232968397
线程-3 开始时间:1609232968397
上面的例子可以看到CyclicBarrier 能达到和CountDownLatch同样的效果,下面再通过一个实例来看看CyclicBarrier 的复用性,假设任务由步骤一和步骤二组成,必须完成步骤一以后才能开始执行步骤二
public class CyclieBarrierTest {
public static void main(String[] args) {
int n = 3;
CyclicBarrier cyclicBarrier = new CyclicBarrier(n, new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " 唤醒线程时间:" + System.currentTimeMillis());
}
});
for (int i = 0; i < n; i++) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
cyclicBarrier.await();
System.out.println(Thread.currentThread().getName() + " 执行步骤一时间:" + System.currentTimeMillis());
Thread.sleep(1000);
//等待步骤一完成
cyclicBarrier.await();
System.out.println(Thread.currentThread().getName() + " 执行步骤二时间:" + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
});
thread.setName("线程-" + i);
thread.start();
}
}
}
运行结果如下:可以看出每个线程在执行完步骤一以后都调用了cyclicBarrier.await()方法,此时会等待所有线程完成步骤一以后再执行步骤二
线程-2 唤醒线程时间:1609234443520
线程-2 执行步骤一时间:1609234443520
线程-1 执行步骤一时间:1609234443520
线程-0 执行步骤一时间:1609234443520
线程-2 唤醒线程时间:1609234444521
线程-2 执行步骤二时间:1609234444521
线程-1 执行步骤二时间:1609234444521
线程-0 执行步骤二时间:1609234444521
原理分析
CyclicBarrier基于独占锁实现,本质底层还是基于AQS,其内部维护了count和parties两个变量,这也就是为什么CyclicBarrier能复用的原因,一开始的时候count等于parties,每当有线程调用await()方法时,count就减1,当count等于0时所有线程开始执行,同时会将parties的值赋给count,从而进行复用。
public CyclicBarrier(int parties, Runnable barrierAction) {
if (parties <= 0) throw new IllegalArgumentException();
this.parties = parties;
this.count = parties;
this.barrierCommand = barrierAction;
}
构造函数给parties和count赋值的同时还给barrierCommand进行了赋值,barrierCommand代表了所有任务到达屏障点时所执行的任务。
private static class Generation {
boolean broken = false;
}
其内部类Generation维护了一个变量broken用来记录当前屏障是否被打破