CyclicBarrier这个工具类要做的事情是让一组线程到达同一个屏障时被阻塞,直到最后一个线程到达屏障时屏障才会开门,所有被屏障拦截的线程才会继续运行。
测试代码如下:
import java.util.concurrent.CyclicBarrier;
/**
* @Description
* @Author DJZ-WWS
* @Date 2019/5/27 15:36
*/
public class cyclicBarrierTest {
static CyclicBarrier cyclicBarrier=new CyclicBarrier(2);
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
try {
cyclicBarrier.await();
}catch (Exception e){
}
System.out.println(1);
}
}).start();
try {
cyclicBarrier.await();
}catch (Exception e){
}
System.out.println(2);
}
}
结果可能有两种
1
2
或者
2
1
因为主线程和子线程的调度是由CPU决定的,两个线程都有可能先执行,所以会产生两种结果。
如果把new CycleBarrier(2)改成new Cyclebarrier(3),则主线程和子线程会永远等待,因为没有第三个线程执行await方法,即没有第三个线程到达屏障,所以之前到达屏障的两个线程都不会继续执行。
为了更清楚的说明工具类的作用把代码做了如下调整:
import java.util.concurrent.CyclicBarrier;
/**
* @Description
* @Author DJZ-WWS
* @Date 2019/5/27 15:36
*/
public class cyclicBarrierTest {
static CyclicBarrier cyclicBarrier=new CyclicBarrier(3);
public static void main(String[] args) {
new Thread(() -> {
try {
System.out.println("1将要等待");
cyclicBarrier.await();
System.out.println("1睡醒了");
}catch (Exception e){
}
System.out.println(1);
}).start();
new Thread(() -> {
try {
System.out.println("2将要等待");
cyclicBarrier.await();
System.out.println("2睡醒了");
}catch (Exception e){
}
System.out.println(2);
}).start();
try {
System.out.println("main将要等待");
cyclicBarrier.await();
}catch (Exception e){
}
System.out.println(3);
}
}
结果如下:
三个线程在调用await方法之前都是做自己的事情一旦调用了await方法那么就会等待其他线程将await方法之前的任务完成,等到所有的线程await前任务都完成了,那么他们开始执行自己各自的任务。
CycleBarrier提供一个更高级的构造函数CycleBarrier(int parties,Runnable barrierAction)
用于在线程到达屏障时,优先执行barrierAction,方便处理更复杂的业务场景。
import java.util.concurrent.CyclicBarrier;
/**
* @Description
* @Author DJZ-WWS
* @Date 2019/5/27 15:36
*/
public class cyclicBarrierTest {
static CyclicBarrier cyclicBarrier = new CyclicBarrier(2,new A());
public static void main(String[] args) {
new Thread(() -> {
try {
System.out.println("1将要等待");
cyclicBarrier.await();
System.out.println("1睡醒了");
} catch (Exception e) {
}
System.out.println(1);
}).start();
try {
System.out.println("main将要等待");
cyclicBarrier.await();
} catch (Exception e) {
}
System.out.println(3);
}
static class A implements Runnable {
@Override
public void run() {
System.out.println("2");
}
}
}
执行结果如下:
他们都会进入等待状态,到达等待状态以后开始执行自己的任务时主线程在A和第一个线程都执行完毕之后才会执行主线程。
CycleBarrier的应用:
CycleBarier 可以用于多线程计算数据,最后合并计算结果的场景。
如用一个Excel保存了用户所有银行流水,每个sheet保存一个账户近一年的每笔银行流水 ,现在需要统计用户的日均银行流水,先用多线程处理每个sheet里的银行流水,现在需要统计用户的日均银行流水,最后,再用barrierAction用这些线程的计算结果,计算出整个Exel的日均银行流水。代码实现如下:
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
/**
* @Description
*
* CycleBarier 可以用于多线程计算数据,最后合并计算结果的场景。
* 如用一个Excel保存了用户所有银行流水,每个sheet保存一个账户近一年的每笔银行流水
* ,现在需要统计用户的日均银行流水,先用多线程处理每个sheet里的银行流水,
* 现在需要统计用户的日均银行流水,最后,再用barrierAction用这些线程的计算结果,
* 计算出整个Exel的日均银行流水
* @Author DJZ-WWS
* @Date 2019/5/27 16:48
*/
public class BankWaterService implements Runnable {
/**
* 创建4个屏障,处理完之后执行当前类的run方法
*/
private CyclicBarrier c=new CyclicBarrier(4,this);
/**
* 假设只有4个sheet,所以只启动4线程
*/
private Executor executor=Executors.newFixedThreadPool(4);
/**
* 保存每个sheet的计算结果
*/
private ConcurrentHashMap<String,Integer> sheetBankWaterCountMap=new ConcurrentHashMap<>();
private void count(){
for (int i = 0; i <4 ; i++) {
executor.execute(() -> {
//计算当前sheet的银行流水数据,计算代码省略
sheetBankWaterCountMap.put(Thread.currentThread().getName(),1);
//银行流水计算完成,插入一个屏障
try {
c.await();
System.out.println("执行其他任务");
}catch (Exception e){
e.printStackTrace();
}
});
}
}
@Override
public void run() {
int result =0;
for (Map.Entry<String,Integer> sheet:sheetBankWaterCountMap.entrySet()){
result+=sheet.getValue();
}
//将结果输出
sheetBankWaterCountMap.put("result",result);
System.out.println(result);
}
public static void main(String[] args) {
/**
* 每个线程都去执行自己的计算任务,待每个线程计算完毕,主线程开始计算汇总。然后再去执行await后面的其他任务
*/
BankWaterService bankWaterService=new BankWaterService();
bankWaterService.count();
}
}
执行结果:
CountDownLatch和CycleBarrier的区别
CountDownLatch计数器只能使用一次,Cyclebarrier(0可以使用reset的方法重置。CycleBarrier能处理更复杂业务场景。
比如:如果计算错误,可以重置计算器,并让线程重新执行一次。
CycleBarrier提供了其他有用的方法,比如getNumWaiting方法可以获得Cyclebarrier阻塞的线程数量。isBroken()方法用来了解阻塞队列的线程是否被中断。
参考:《java 并发编程的艺术》