Java并发编程之CyclicBarrier

115 阅读3分钟

简介

上一篇文章介绍了CountDownLatch可以用来计数,另一个也可以实现此功能的就是CyclicBarrier,而且与CountDownLatch相比最大的优点就是可以重复使用,下面通过几种情况来介绍CyclicBarrier的使用。

计数等待

@Slf4j
public class Test5 {
    public static void main(String[] args) {
        CyclicBarrier barrier = new CyclicBarrier(2);
        new Thread(() -> {
            try {
                log.info("t1 执行中...");
                Thread.sleep(1000);
                barrier.await();
                log.info("t1 执行结束");
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }, "t1").start();
        new Thread(() -> {
            try {
                log.info("t2 执行中...");
                Thread.sleep(2000);
                barrier.await();
                log.info("t2 执行结束");
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }, "t2").start();
    }
}

Snipaste_2023-05-11_20-22-08.png

上面创建了一个CyclicBarrier对象,并且初始化计数为2。创建两个子线程t1t2执行任务并调用await方法进行等待,让t1睡眠1秒,t2睡眠2秒。根据最后输出结果可以看到t1t2都是2秒后才继续执行的,也就是说当t1调用barrier.await()方法之后就开始阻塞等待,直到计数变为0之后才会继续执行。

使用CountDownLatch时,子线程调用countDown方法之后会立即返回,继续向下执行,不会等待其他线程。

结束处理

@Slf4j
public class Test5 {
    public static void main(String[] args) {
        CyclicBarrier barrier = new CyclicBarrier(2, () -> log.info("子线程执行结束了"));
        new Thread(() -> {
            try {
                log.info("t1 执行中...");
                Thread.sleep(1000);
                barrier.await();
                log.info("t1 执行结束");
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }, "t1").start();
        new Thread(() -> {
            try {
                log.info("t2 执行中...");
                Thread.sleep(2000);
                barrier.await();
                log.info("t2 执行结束");
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }, "t2").start();
    }
}

Snipaste_2023-05-11_20-26-35.png CyclicBarrier的构造方法中可以传入一个Runnable任务,当计数变为0之后就会执行这个任务,而且要优先于被唤醒的子线程继续执行,这点和CountDownLatch的使用就很相似了。

循环使用

@Slf4j
public class Test5 {
    public static void main(String[] args) {
        CyclicBarrier barrier = new CyclicBarrier(2, () -> log.info("子线程执行结束了"));
        ExecutorService service = Executors.newFixedThreadPool(2);
        for (int i = 0; i < 3; i++) {
            service.submit(() -> {
                try {
                    log.info("线程t1执行中");
                    barrier.await();
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            });
            service.submit(() -> {
                try {
                    log.info("线程t2执行中");
                    barrier.await();
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            });
        }
        service.shutdown();
    }
}

Snipaste_2023-05-11_20-34-00.png 上面创建了一个最大线程数为2的线程池,然后循环三次每次提交两个任务,根据输出结果可以看出一共执行了三次,也就是子线程把计数变为0之后又重新执行了任务,这是因为CyclicBarrier的计数变成0之后会重新把它修改成初始值,这里就是2,所以第二次循环时子线程又可以调用await方法来把计数减1,这样一直循环使用,这也是CyclicBarrier的最大特点。

超时等待

@Slf4j
public class Test5 {
    public static void main(String[] args) throws InterruptedException {
        CyclicBarrier barrier = new CyclicBarrier(2, () -> log.info("子线程执行结束了"));
        Thread t1 = new Thread(() -> {
            try {
                log.info("线程t1执行中");
                barrier.await(1, TimeUnit.SECONDS);
                log.info("线程t1执行结束");
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
        t1.start();
        Thread t2 = new Thread(() -> {
            try {
                log.info("线程t2执行中");
                Thread.sleep(3000);
                barrier.await();
                log.info("线程t2执行结束");
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
        t2.start();
    }
}

Snipaste_2023-05-11_20-56-45.png 让线程t1调用await时只等待1秒,让t2先睡眠3秒再调用await方法。输出结果中可以看到t1线程在1秒后抛出了TimeoutException异常,而t2线程在2秒后抛出了BrokenBarrierException异常。当有一个线程破坏了CyclicBarrier的计数规则之后,其他线程也将不再遵守规则来等待计数变为0,而是直接抛出BrokenBarrierException异常。