前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >五分钟教会你JUC中的“CountDownLatch”和“CyclicBarrier”应该如何使用

五分钟教会你JUC中的“CountDownLatch”和“CyclicBarrier”应该如何使用

作者头像
程序员牛肉
发布2024-09-30 16:14:38
940
发布2024-09-30 16:14:38
举报
文章被收录于专栏:小牛肉带你学Java

大家好,我是程序员牛肉。

JUC作为Java面试的必考板块,其重要性不言而喻。学习JUC包下的常用类不仅仅是在学习这些类怎么使用,更是在学习这些类中所蕴藏的设计思维。

而今天我们要介绍的两个类分别是“CountDownLatch”和“CyclicBairrier”。

我们先来介绍CountDownLatch。我们设想这样一个业务场景:我们的代码中需要执行三个任务A,B,C。

在这其中,任务B的执行需要A,C任务执行得到的结果。那么最简单的执行逻辑就应该是这样:

可是这样串行执行也太low了。身为一名合格的程序员,我必须使用多线程了:我们把任务A和任务C调成为子线程异步执行。

于是我们的执行逻辑变成了这样:

可这样也不对啊,还记得我们之前说过任务B的执行需要依赖任务A和任务C的执行结果吗?

主线程是没办法直接执行任务B的。也就是说在我们异步处理执行任务A和任务C的同时,还要设计代码逻辑使得主进程等待任务A和任务C的执行完毕。

在主线程内使用join方法吗?这也太low了。而且这段代码会频繁的创建两个线程用来异步执行任务A和C。

[在 Java 中,join 方法是 Thread 类的一个实例方法,它的作用是让当前线程等待调用 join 方法的线程终止。换句话说,如果一个线程 A 调用了另一个线程 B 的 join 方法,那么线程 A 会一直等待,直到线程 B 执行完毕。]

代码语言:javascript
复制
public class TaskExecution {
    public static void main(String[] args) {
        // 创建线程执行任务 A
        Thread threadA = new Thread(() -> {
              执行任务A
        });

        // 创建线程执行任务 C
        Thread threadC = new Thread(() -> {
              执行任务C
        });

        // 启动任务 A 和 C 的线程
        threadA.start();
        threadC.start();
      
        threadA.join();
        threadC.join();

        // 任务 A 和 C 完成后执行任务 B
       执行任务B  
        System.out.println("任务 B 开始执行");
    }
}

为了避免频繁创建和销毁线程所带来的性能消耗,我们想到了线程池。可是如果使用线程池就又会出现一个问题:

我们本来设计使用join来等待任务A和任务C的结束,但是对线程池的线程使用Join会存在很多的隐患。

比如由于线程池中的线程一直处于复用状态,可能不会真正的退出。那么我们的Join就没有办法准确的检测到任务A和任务C的执行完成。

我们得重新设计一种方案了。其实很容易就能想到计数器:

我们可以搞一个计数器,计数器的初始值设置为2。无论是任务A还是任务C执行完毕,都要在方法内部对这个计数器减一。

而我们的主线程会一直自旋等待这个计数器。只有当计数器的值为0的时候,主线程才会执行方法B。

仔细一想,这个方案其实还挺优秀的。他在一定程度上实现了主线程需要等待任务A和任务C执行完毕的代码逻辑。

如果你能够想到这里,恭喜你设计出了“CountDownLatch”。

[CountDownLatch 是 Java 并发包 java.util.concurrent 中的一个同步辅助类,它主要用于控制一个或多个线程等待其他线程完成某些操作。CountDownLatch 通过一个计数器来实现线程之间的协调,计数器的初始值等于需要等待的事件的数量。]

基于CountDownLatch,我们可以爆改我们的那段代码:

代码语言:javascript
复制
public class TaskExecution {
    public static void main(String[] args) throws InterruptedException {
        // 创建 CountDownLatch,初始计数为 2
        CountDownLatch latch = new CountDownLatch(2);

        // 创建线程执行任务 A
        Thread threadA = new Thread(() -> {
            执行任务A
           // A 完成后调用 countDown
            latch.countDown(); 
        });

        // 创建线程执行任务 C
        Thread threadC = new Thread(() -> {
            执行任务C
           // C 完成后调用 countDown
            latch.countDown(); 
        });

        // 启动任务 A 和 C 的线程
        threadA.start();
        threadC.start();

        // 主线程等待 A 和 C 完成
        latch.await();

        // 任务 A 和 C 完成后执行任务 B
        System.out.println("任务 B 开始执行");
         执行任务B       
    }
}

让我们最后总结一下CountDownLatch有哪些特点:

  • 计数器:CountDownLatch 内部有一个计数器,用于控制等待的事件数量。
  • 一次性:CountDownLatch 只能使用一次,计数器的值减到零后不能再被重置。
  • 阻塞等待:调用 await() 方法的线程会阻塞,直到计数器的值减到零。
  • 递减计数器:通过调用 countDown() 方法递减计数器的值。

需要注意的是,CountDownLatch是一次性的。当一个CountDown被使用完毕之后,我们是没有办法通过重新赋值来重新使用它的。只能重新new一个CountDownLatch。

CountDown就介绍到这里了,下面让我们介绍一下CyclicBairrier。

如果说CountDownLatch强调的是让单个或多个线程等待一组任务的完成,那么CyclicBarrier强调的就是让一组线程互相等待,直到所有线程都到达某个点。

他听起来和CountDownLatch的作用差不多。二者的主要区别也在于CyclicBarrier是可以复用的。

围绕在CyclicBarrier这个类上的主要有两个点:

  • 可重用:CyclicBarrier 可以在所有线程都到达屏障点后重新使用,这与 CountDownLatch 不同,后者在计数器到达零后不能重用。
  • 屏障操作:CyclicBarrier 可以在所有线程到达屏障点时执行一个可选的屏障操作(Barrier Action),这个操作由最后一个到达屏障点的线程执行。

我们可以简单的写一个代码案例,创建三个线程来相互等待。

代码语言:javascript
复制
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierExample {

    public static void main(String[] args) {
        int parties = 3;
        CyclicBarrier barrier = new CyclicBarrier(parties, new Runnable() {
            @Override
            public void run() {
                System.out.println("所有线程都到达屏障点,继续执行。");
            }
        });

        for (int i = 0; i < parties; i++) {
            new Thread(new Task(barrier)).start();
        }
    }
}

class Task implements Runnable {
    private CyclicBarrier barrier;

    public Task(CyclicBarrier barrier) {
        this.barrier = barrier;
    }

    @Override
    public void run() {
        try {
            System.out.println(Thread.currentThread().getName() + " 正在等待屏障。");
            barrier.await();
            System.out.println(Thread.currentThread().getName() + " 已经跨过屏障。");
        } catch (InterruptedException | BrokenBarrierException e) {
            e.printStackTrace();
        }
    }
}

相信通过我的介绍,你已经大致了什么是“CountDownLatch”和“CyclicBarrier”。希望我的文章可以帮到你。

关于这两个JUC下的常用类,你有什么想说的嘛?欢迎在评论区留言。

关注我,带你了解更多计算机干货。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-09-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序员牛肉 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档