Java并发编程--JUC并发工具类之CountDownLatch

摘要

  • 本文介绍CountDownLatch相关技术

  • 本文基于jdk1.8

CountDownLatch介绍

  • CountDownLatch提供了一种同步机制,用于在多个线程之间进行等待和协调。

  • CountDownLatch是一种计数器,它允许一个或多个线程等待其他线程完成操作后再继续执行。

  • CountDownLatch的工作原理很简单,它通过一个初始计数器来初始化,该计数器表示需要等待的线程数。当一个线程完成了它的操作时,计数器会减少。当计数器达到零时,所有等待的线程都会被释放,可以继续执行。

CountDownLatch的常用方法

方法 描述
CountDownLatch(int count) 构造函数,创建一个CountDownLatch对象,指定初始计数器的值为count
void await() 阻塞调用此方法的线程,直到计数器达到零。如果计数器的值不为零,则调用线程将被阻塞
boolean await(long timeout, TimeUnit unit) 阻塞调用此方法的线程,直到计数器达到零或超过指定的等待时间。如果计数器的值不为零,则调用线程将被阻塞
void countDown() 计数器减少一个单位。当一个线程完成了它的操作时,应该调用此方法来减少计数器的值
long getCount() 获取当前计数器的值

代码示例

示例1–多任务完成后合并汇总

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import java.util.concurrent.CountDownLatch;

public class CountDownLatchExample {
public static void main(String[] args) {
final int THREAD_COUNT = 3;
final CountDownLatch latch = new CountDownLatch(THREAD_COUNT);

// 创建并启动三个线程
for (int i = 1; i <= THREAD_COUNT; i++) {
Thread thread = new Thread(new Worker(latch, "Thread " + i)); // 创建Worker对象并传递CountDownLatch和线程名称
thread.start(); // 启动线程
}

try {
latch.await(); // 主线程等待所有线程完成任务
System.out.println("All threads have completed their tasks.");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

class Worker implements Runnable {
private final CountDownLatch latch;
private final String name;

public Worker(CountDownLatch latch, String name) {
this.latch = latch; // 初始化CountDownLatch
this.name = name; // 初始化线程名称
}

@Override
public void run() {
System.out.println(name + " has started its task.");
// 模拟工作
try {
Thread.sleep(2000); // 线程休眠2秒,模拟执行任务的耗时操作
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + " has completed its task.");
latch.countDown(); // 任务完成后计数器减少
}
}
  • 在上述示例中,主线程创建了一个CountDownLatch对象,并指定初始计数器的值为3。然后,主线程启动了三个工作线程,并将CountDownLatch对象传递给它们。

  • 每个工作线程在执行任务前会输出开始信息,然后模拟执行任务的耗时操作(这里用Thread.sleep(2000)模拟),最后输出任务完成信息。在任务完成后,每个工作线程都会调用latch.countDown()来减少计数器的值。

  • 主线程通过调用latch.await()方法阻塞自己,等待所有工作线程完成任务。当计数器的值为零时,主线程被唤醒,继续执行,输出"All threads have completed their tasks."的信息。

示例2–模拟实现百米赛跑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class CountDownLatchDemo {
private static CountDownLatch begin = new CountDownLatch(1); // begin 代表裁判 初始为 1
private static CountDownLatch end = new CountDownLatch(8); // end 代表玩家 初始为 8

public static void main(String[] args) throws InterruptedException {
for (int i = 1; i <= 8; i++) {
new Thread(new Runnable() {
@SneakyThrows
@Override
public void run() {
// 预备状态
System.out.println("参赛者" + Thread.currentThread().getName() + "已经准备好了");
// 等待裁判吹哨,这里就类似于多个子线程等待某个共享资源的初始化完成后再开始执行。
begin.await();
// 开始跑步
System.out.println("参赛者" + Thread.currentThread().getName() + "开始跑步");
Thread.sleep(1000);
// 跑步结束, 跑完了
System.out.println("参赛者" + Thread.currentThread().getName() + "到达终点");
// 跑到终点, 计数器就减一
end.countDown();
}
}).start();
}
// 等待 5s 就开始吹哨
Thread.sleep(5000);
System.out.println("开始比赛");
// 裁判吹哨, 计数器减一
begin.countDown();
// 等待所有玩家到达终点,即主线程等待多个子线程完成任务后再继续执行。
end.await();
System.out.println("比赛结束");
}
}
  • 上述代码展示了使用CountDownLatch进行比赛场景的模拟。裁判(begin)初始计数为1,代表裁判等待的条件为所有参赛者准备就绪。玩家(end)初始计数为8,代表参赛者的数量。

  • 每个参赛者都会在准备好后等待裁判的指令,然后开始跑步,跑到终点后计数器减一。裁判会在等待5秒后吹哨,开始比赛。当所有玩家到达终点后,比赛结束。

CountDownLatch通常用于以下场景

  • 主线程等待多个子线程完成任务后再继续执行。

  • 多个子线程等待某个共享资源的初始化完成后再开始执行。

  • 协调多个线程执行的时间点。

使用CountDownLatch的注意事项

  • 初始化计数器的值:在创建CountDownLatch对象时,需要确定初始计数器的值。该值应该与等待的线程数量一致,以确保所有线程完成任务后计数器的值为0。

  • await()方法的阻塞:调用await()方法会使当前线程阻塞,直到计数器的值变为0。需要确保在调用await()方法之前,计数器的值能够达到0。否则,调用线程将一直被阻塞,无法继续执行。

  • countDown()方法的调用:在每个线程完成任务后,都需要调用countDown()方法来减少计数器的值。确保在适当的位置调用countDown(),否则计数器的值无法正确减少,导致主线程一直被阻塞。

  • 异常处理:在使用CountDownLatch时,需要注意处理可能抛出的InterruptedException异常。例如,在调用await()方法时需要捕获InterruptedException,并进行适当的处理。

  • CountDownLatch的复用:CountDownLatch对象在计数器值减少到0后,可以重用。可以通过重新设置计数器的值来复用CountDownLatch对象,以便在新的场景中再次使用。

  • 计数器值的访问:可以使用getCount()方法获取当前计数器的值,以便在需要时查看计数器的状态。