前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >Java多线程下,线程之间如何实现互相等待

Java多线程下,线程之间如何实现互相等待

原创
作者头像
叫我阿柒啊
发布2025-02-19 18:02:01
发布2025-02-19 18:02:01
2762
举报

前言

在我们刚学Java的时候,程序在main中自上而下、顺序执行。后来在实习中接触到Java多线程的应用,多线程充分利用了单台服务器的计算资源,然后就根据服务器的load和cpu核数来调整程序的线程数。

当时对多线程可能了解不太得深入,只能通过服务器负载来调整现成的数量。从当时的应用效果来看,对于线程之间不需要进行交互的应用场景来说(例如处理Kafka的数据),多线程技术真的是简单好用。

但是对于线程之间需要交互的场景来说,例如Thread A、B、C共同完成一个工作,如果A干完了需要等待B、C一起完成,如果自己实现的话就比较麻烦,所以今天就来介绍能够满足这样场景的并发工具类:CountDownLatchCyclicBarrier

CountDownLatch

CountDownLatch可以理解成倒计时计数器,它可以让多个线程等待某些任务完成后再继续执行。如果你以前遇到过“等所有子任务执行完再汇总结果”的问题。

使用

我们使用 new CountDownLatch(N)创建对象时,构造函数中的N就表示计数器,这里和线程数是一致的。

对于开发者来说,我们只关注两个方法:

  1. countDown() :当一个任务完成时,调用让计数器(N)减1:
  2. await() :让当前线程等待,直到计数器变成0才能继续执行

如果不想无限等待,可以指定超时时间:

代码语言:java
复制
latch.await(5, TimeUnit.SECONDS);

上面代码表示,如果 5 秒内 countDown() 没有执行完,await() 会自动超时并继续执行。

举个栗子🌰

这里举个例子,假设你是一个老师,考试结束后,你必须等所有学生交卷(countDown())后,才能收卷走人(await())。

我们先定义一个学生类Student:

代码语言:java
复制
class Student implements Runnable {
    private CountDownLatch latch;
    private int id;

    public Student(CountDownLatch latch, int id) {
        this.latch = latch;
        this.id = id;
    }

    @Override
    public void run() {
        System.out.println("学生 " + id + " 交卷");
        latch.countDown(); // 交卷后,计数器减 1
    }
}

在Student类中,通过构造参数传入CountDownLatch,并实现Runnable线程类的run(),通过调用countDown() 完成N - 1来模拟学生的交卷动作。然后实现一个主类,主线程main就扮演老师的角色。

代码语言:java
复制
public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        int studentCount = 5;
        CountDownLatch latch = new CountDownLatch(studentCount);

        for (int i = 1; i <= studentCount; i++) {
            new Thread(new Student(latch, i)).start();
        }
        // 等待所有学生交卷(N变成0)
        latch.await(); 
        System.out.println("老师收卷,考试结束!");
    }
}

在代码中创建五个学生(线程),然后启动程序。我们知道,在main中的代码是顺序执行的,正常情况下,创建并启动线程之后,main函数是接着执行的,也就是说,我不管学生交卷与否,我直接收卷结束考试。

但是在上面main函数中,添加了一行代码 latch.await() ,也就意味着必须要等到CountDownLatch中的N变成0(即所有人交卷),才能继续执行,如图所示:

我们知道多线程的情况下,每个线程执行完成的顺序是没有规律的,但是不论谁先完成都要等待。

CyclicBarrier

在多线程并发工具中,除了CountDownLatch之外,CyclicBarrier也具有这种”等待“功能,CyclicBarrier翻译为循环屏障,但与CountDownLatch的使用上游不同之处:

特性

CountDownLatch

CyclicBarrier

计数方式

只减不加,不能重用

可重置,循环使用

线程等待

await() 等待计数器变 0

await() 等待所有线程到达屏障点

适用场景

某个任务要等待多个线程完成

多个线程需要同步执行某个任务

简单来说:

  1. CyclicBarrier可复用
  2. CyclicBarrier是线程本身等待,CountDownLatch是主线程等待

使用

可能上面说的很难理解,我们先从用法开始看起:

  1. CyclicBarrier(N):设置需要等待的线程数
  2. await():线程到达屏障,N - 1并等待其他线程

从用法中可以看出,await 既能使计数器 -1,还能等待,那么CountDownLatch中,计数器-1是在线程中调用的,那么我们就在线程中调用await()。

举个栗子🌰

代码语言:java
复制
class Student implements Runnable {
    private CyclicBarrier cyclicBarrier;
    private int id;

    public Student(CyclicBarrier cyclicBarrier, int id) {
        this.cyclicBarrier = cyclicBarrier;
        this.id = id;
    }

    @SneakyThrows
    @Override
    public void run() {
        System.out.println("学生 " + id + " 交卷");
        cyclicBarrier.await();
    }
}

此时,我们在main线程中也调用await等待所有线程完成。

代码语言:java
复制
public static void main(String[] args) throws InterruptedException {
    int studentCount = 5;
    CyclicBarrier cyclicBarrier = new CyclicBarrier(studentCount + 1);

    for (int i = 1; i <= studentCount; i++) {
        new Thread(new Student(cyclicBarrier, i)).start();
    }
    
    cyclicBarrier.await();
    System.out.println("老师收卷,考试结束!");
}

与CountDownLatch不同的是,有5个学生,但是这里的N是6,因为老师也要等待所有学生完成之后,自己才能结束考试。

思考

同样一个交卷场景下,CountDownLatch用的就挺舒服,但是CyclicBarrier就比较别扭,因为你想,考试的情况下,明明只要老师等待所有人交卷结束考试即可,为什么学生之间还需要等待呢?

所以CyclicBarrier比较适合的场景是:学生互相等待一起做一件事情。同样是考试,老师要求必须所有学生到齐了才能发卷考试,我们将上面run()中的“交卷”改为“到达教室”,然后实现main函数。

代码语言:java
复制
public static void main(String[] args) {
        int studentCount = 5;
        CyclicBarrier cyclicBarrier = new CyclicBarrier(studentCount, () -> {
            System.out.println("开始考试");
        });

        for (int i = 1; i <= studentCount; i++) {
            new Thread(new Student(cyclicBarrier, i)).start();
        }
    }
}

在上面main中无需调用await等待。在CyclicBarrier我们除了传入N,还可以传入一个回调函数,当N为0时,会执行回调函数。所以CyclicBarrie更适合线程之间等待的场景,代码执行结果如下:

结语

什么时候用 CountDownLatch?

  • 需要等待一组线程执行完后,再执行主任务
  • 比如 多个服务启动后,系统才能运行

什么时候用 CyclicBarrier?

  • 需要多个线程彼此等待,然后一起执行
  • 比如 多人组队游戏,所有玩家加载完成后,游戏才能开始

CountDownLatch参与主体是线程和主线程,线程之间不需要等待,执行任务的主体主线程。CyclicBarrier参与主体只有线程,线程之间需要互相等待,执行任务的主体是线程本身。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

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

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
    • CountDownLatch
    • 使用
    • 举个栗子🌰
  • CyclicBarrier
    • 使用
    • 举个栗子🌰
    • 思考
  • 结语
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档