首页
技术小册
AIGC
面试刷题
技术文章
MAGENTO
云计算
视频课程
源码下载
PDF书籍
「涨薪秘籍」
登录
注册
01 | 可见性、原子性和有序性问题:并发编程Bug的源头
02 | Java内存模型:看Java如何解决可见性和有序性问题
03 | 互斥锁(上):解决原子性问题
04 | 互斥锁(下):如何用一把锁保护多个资源?
05 | 一不小心就死锁了,怎么办?
06 | 用“等待-通知”机制优化循环等待
07 | 安全性、活跃性以及性能问题
08 | 管程:并发编程的万能钥匙
09 | Java线程(上):Java线程的生命周期
10 | Java线程(中):创建多少线程才是合适的?
11 | Java线程(下):为什么局部变量是线程安全的?
12 | 如何用面向对象思想写好并发程序?
13 | 理论基础模块热点问题答疑
14 | Lock和Condition(上):隐藏在并发包中的管程
15 | Lock和Condition(下):Dubbo如何用管程实现异步转同步?
16 | Semaphore:如何快速实现一个限流器?
17 | ReadWriteLock:如何快速实现一个完备的缓存?
18 | StampedLock:有没有比读写锁更快的锁?
19 | CountDownLatch和CyclicBarrier:如何让多线程步调一致?
20 | 并发容器:都有哪些“坑”需要我们填?
21 | 原子类:无锁工具类的典范
22 | Executor与线程池:如何创建正确的线程池?
23 | Future:如何用多线程实现最优的“烧水泡茶”程序?
24 | CompletableFuture:异步编程没那么难
25 | CompletionService:如何批量执行异步任务?
26 | Fork/Join:单机版的MapReduce
27 | 并发工具类模块热点问题答疑
28 | Immutability模式:如何利用不变性解决并发问题?
29 | Copy-on-Write模式:不是延时策略的COW
30 | 线程本地存储模式:没有共享,就没有伤害
31 | Guarded Suspension模式:等待唤醒机制的规范实现
32 | Balking模式:再谈线程安全的单例模式
33 | Thread-Per-Message模式:最简单实用的分工方法
34 | Worker Thread模式:如何避免重复创建线程?
35 | 两阶段终止模式:如何优雅地终止线程?
36 | 生产者-消费者模式:用流水线思想提高效率
37 | 设计模式模块热点问题答疑
38 | 案例分析(一):高性能限流器Guava RateLimiter
39 | 案例分析(二):高性能网络应用框架Netty
40 | 案例分析(三):高性能队列Disruptor
41 | 案例分析(四):高性能数据库连接池HiKariCP
42 | Actor模型:面向对象原生的并发模型
43 | 软件事务内存:借鉴数据库的并发经验
44 | 协程:更轻量级的线程
45 | CSP模型:Golang的主力队员
当前位置:
首页>>
技术小册>>
Java并发编程实战
小册名称:Java并发编程实战
### 19 | CountDownLatch和CyclicBarrier:如何让多线程步调一致? 在Java并发编程中,确保多个线程能够按照预定的顺序或条件协同工作是一项重要而复杂的任务。`CountDownLatch`和`CyclicBarrier`是Java并发包(`java.util.concurrent`)中提供的两个非常有用的工具类,它们各自以不同的方式帮助开发者实现线程间的同步,确保线程能够“步调一致”地执行。本章将深入探讨这两个类的使用场景、工作原理、以及它们如何帮助我们在多线程环境中控制线程的执行顺序。 #### 1. CountDownLatch:等待直到所有任务完成 `CountDownLatch`是一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。`CountDownLatch`的工作原理类似于倒计时器:初始化时设置一个计数器(count),每当一个线程完成了它的任务,计数器的值就减一(通过调用`countDown()`方法)。当计数器的值达到零时,所有等待的线程(通过调用`await()`方法)将被唤醒继续执行。 ##### 1.1 使用场景 - **并行计算**:当需要将一个大任务拆分成多个小任务并行执行,并且需要等待所有小任务完成后才能继续执行后续操作时。 - **资源初始化**:在应用程序启动时,需要等待多个资源(如数据库连接、配置文件加载等)都准备好后才能继续执行。 ##### 1.2 示例代码 ```java import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class CountDownLatchExample { public static void main(String[] args) throws InterruptedException { int numThreads = 5; CountDownLatch latch = new CountDownLatch(numThreads); ExecutorService executor = Executors.newFixedThreadPool(numThreads); for (int i = 0; i < numThreads; i++) { executor.submit(() -> { try { // 模拟任务执行 Thread.sleep(1000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } latch.countDown(); // 任务完成,计数器减一 }); } latch.await(); // 等待所有任务完成 System.out.println("所有任务完成"); executor.shutdown(); } } ``` #### 2. CyclicBarrier:让一组线程相互等待 与`CountDownLatch`不同,`CyclicBarrier`允许一组线程相互等待,直到达到某个公共屏障点(common barrier point)。所有线程都必须调用`await()`方法,然后这些线程将在屏障处被阻塞,直到最后一个线程到达。当最后一个线程到达屏障时,所有线程将被释放,继续执行它们的`await()`调用之后的代码。值得注意的是,`CyclicBarrier`是可以重用的,即一旦所有线程都被释放,它可以被重置并再次使用,无需重新创建新的对象。 ##### 2.1 使用场景 - **阶段性同步**:在算法或任务的执行过程中,需要将执行过程分为几个阶段,每个阶段完成后才能进行下一个阶段。 - **并行迭代**:当多个线程需要迭代相同的数据集,但每个线程只能处理数据的一部分,并且在所有部分都处理完成后才能进行下一步操作。 ##### 2.2 示例代码 ```java import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; public class CyclicBarrierExample { public static void main(String[] args) { int numThreads = 4; CyclicBarrier barrier = new CyclicBarrier(numThreads, () -> { System.out.println("所有线程都到达了屏障点,继续执行后续任务"); }); for (int i = 0; i < numThreads; i++) { new Thread(() -> { try { // 模拟任务执行 Thread.sleep(1000); System.out.println(Thread.currentThread().getName() + " 到达屏障点"); barrier.await(); // 等待其他线程 } catch (InterruptedException | BrokenBarrierException e) { Thread.currentThread().interrupt(); } }).start(); } } } ``` #### 3. CountDownLatch与CyclicBarrier的比较 - **用途差异**:`CountDownLatch`主要用于一个或多个线程等待其他多个线程完成某项操作;而`CyclicBarrier`则是让一组线程相互等待,直到它们都达到某个公共屏障点。 - **重用性**:`CountDownLatch`是不可重用的,一旦计数器到达零,就不能再次被使用;而`CyclicBarrier`是可以重用的,每次所有线程通过屏障后,它都可以被重置并再次使用。 - **触发条件**:`CountDownLatch`的触发条件是计数器达到零;而`CyclicBarrier`的触发条件是达到屏障点的线程数量与构造时指定的线程数量一致。 #### 4. 实际应用中的注意事项 - **死锁与性能**:在多线程编程中,不恰当的同步机制可能导致死锁或性能下降。使用`CountDownLatch`和`CyclicBarrier`时,应确保它们的使用不会导致线程永久阻塞或不必要的等待。 - **异常处理**:当线程在等待`CyclicBarrier`时,如果其中一个线程因异常退出,其他线程将抛出`BrokenBarrierException`。因此,在调用`await()`方法时,应准备好处理此异常。 - **中断处理**:在调用`await()`方法时,如果线程被中断,它将抛出`InterruptedException`。在捕获此异常后,通常需要将中断状态重新设置到当前线程,以便上层调用者能够感知到中断。 通过合理使用`CountDownLatch`和`CyclicBarrier`,我们可以有效地控制多线程的执行顺序,实现复杂的并发逻辑。然而,这也要求开发者对Java并发编程有深入的理解,能够准确地把握同步机制的使用时机和方式,以避免潜在的问题。
上一篇:
18 | StampedLock:有没有比读写锁更快的锁?
下一篇:
20 | 并发容器:都有哪些“坑”需要我们填?
该分类下的相关小册推荐:
Java语言基础9-常用API和常见算法
SpringBoot零基础到实战
Java语言基础1-基础知识
Mybatis合辑1-Mybatis基础入门
Java语言基础4-数组详解
java源码学习笔记
Java语言基础11-Java中的泛型
Java语言基础16-JDK8 新特性
Mybatis合辑4-Mybatis缓存机制
Java高并发秒杀入门与实战
Java语言基础12-网络编程
手把手带你学习SpringBoot-零基础到实战