返回

CountDownLatch和CyclicBarrier,面试官都在问什么

后端

并发协作的利器:CountDownLatch与CyclicBarrier

在多线程编程的世界中,协调不同线程之间的执行至关重要。CountDownLatchCyclicBarrier 是Java并发编程中两款出色的工具,能够帮助我们优雅地实现线程之间的协作。

CountDownLatch:一起出发,各自抵达

CountDownLatch 就像一场赛跑的起点,它允许一组线程在完成自己的任务之前同步等待。当所有线程都完成任务后,它们同时继续执行。

用法示例:

CountDownLatch latch = new CountDownLatch(3);

Thread t1 = new Thread(() -> {
    // 任务1
    latch.countDown();
});
Thread t2 = new Thread(() -> {
    // 任务2
    latch.countDown();
});
Thread t3 = new Thread(() -> {
    // 任务3
    latch.countDown();
});

t1.start();
t2.start();
t3.start();

latch.await(); // 主线程等待所有线程完成

// 主线程继续执行

在这个例子中,主线程等待三个线程都完成任务后才继续执行。

使用场景:

  • 等待一组异步任务完成。
  • 确保在所有线程都完成初始化之前不执行其他任务。
  • 控制线程并发执行的数量。

CyclicBarrier:一起出发,一起抵达

CyclicBarrier 类似于接力赛的终点线。它允许一组线程在完成各自的任务后同步等待,然后一起继续执行。与CountDownLatch不同,CyclicBarrier允许线程在完成任务后重新回到起点,等待下一轮执行。

用法示例:

CyclicBarrier barrier = new CyclicBarrier(3);

Thread t1 = new Thread(() -> {
    // 任务1
    barrier.await(); // 线程1等待其他线程完成
});
Thread t2 = new Thread(() -> {
    // 任务2
    barrier.await(); // 线程2等待其他线程完成
});
Thread t3 = new Thread(() -> {
    // 任务3
    barrier.await(); // 线程3等待其他线程完成
});

t1.start();
t2.start();
t3.start();

barrier.await(); // 主线程等待所有线程完成

// 主线程继续执行

使用场景:

  • 等待一组任务循环执行。
  • 确保所有线程在进入下一阶段之前都处于同一状态。
  • 实现线程池中的工作窃取算法。

CountDownLatch与CyclicBarrier的区别

特征 CountDownLatch CyclicBarrier
目的 等待一组线程完成 等待一组线程完成并重新回到起点
重用性 不可重用 可重用
线程状态 线程完成任务后继续执行 线程完成任务后等待其他线程并重新回到起点
使用场景 异步任务、初始化 循环执行、工作窃取

总结

CountDownLatchCyclicBarrier 是协调多线程执行的强大工具。理解它们的差异和使用场景对于构建健壮和高效的多线程应用程序至关重要。

常见问题解答

1. CountDownLatch可以多次使用吗?
不,CountDownLatch不可重复使用。

2. CyclicBarrier可以用于限制并发线程的数量吗?
可以,CyclicBarrier可以通过设置其构造函数中的参数来限制并发线程的数量。

3. CountDownLatch和CyclicBarrier哪个性能更好?
通常情况下,CountDownLatch的性能略优于CyclicBarrier。

4. 什么情况下应该使用CountDownLatch,什么情况下应该使用CyclicBarrier?
如果需要等待一组线程完成任务且不需要它们重新回到起点,则应该使用CountDownLatch。如果需要等待一组线程完成任务并重新回到起点,则应该使用CyclicBarrier。

5. CountDownLatch和Semaphore有什么区别?
CountDownLatch是一个一次性的同步机制,而Semaphore是一个可重用的同步机制。Semaphore允许限制并发线程的数量,而CountDownLatch不允许。