JUC多线程:辅助类CountDownLatch、CyclicBarrier、Semaphore实战指南(附保姆级代码)
📌 文章提示
适合人群:掌握Java基础语法、了解线程基本概念
你将收获:
-
三大并发工具类的核心原理
-
真实应用场景代码实现
-
常见面试题破解技巧
-
多线程协作的架构设计思维
目录
📌 文章提示
🌟 前言
一、CountDownLatch:团队协作倒计时器
1. CountDownLatch 的基本概念
执行过程:
二、CyclicBarrier:循环集结冲锋号
1. CyclicBarrier 的基本概念
代码流程:
三、Semaphore(信号量):资源管控红绿灯
1. Semaphore 的基本概念
执行流程:
🔥 高频面试题破解
Q1:CyclicBarrier和CountDownLatch如何选择?
Q2:Semaphore可以做锁吗?
💡 总结与升华
三大神器对比表
实战技巧锦囊
扩展思考
🌟 前言
在Java并发编程的世界里,有三个如同"交通信号灯"般重要的工具类:
-
🚦 CountDownLatch(倒计时门闩):像运动会起跑前的倒计时
-
🚧 CyclicBarrier(循环栅栏):像组团旅游等人齐发车
-
🚥 Semaphore(信号量):像停车场空位显示牌
本文将用最接地气的比喻+真实代码案例,带你轻松征服这三个并发利器!
一、CountDownLatch:团队协作倒计时器
CountDownLatch 是 Java 并发工具包(java.util.concurrent)中的一种同步辅助类,用于在多线程环境中实现一个线程等待其他多个线程完成操作后再继续执行。
1. CountDownLatch 的基本概念
定义:CountDownLatch 是一个同步工具类,允许一个或多个线程等待其他线程完成操作。
工作原理:
- 初始化时设置一个计数器,计数器的初始值为一个正整数。
- 每当一个线程完成了自己的任务后,调用 countDown() 方法使计数器减 1。
- 主线程或其他等待线程调用 await() 方法,该方法会阻塞直到计数器归零。
- 一旦计数器归零,所有调用 await() 方法的线程将被唤醒,继续执行。
代码示例:
package JUC.add;import java.util.concurrent.CountDownLatch;/*** CountDownLatch:闭锁,在完成某些运算时,只有其他所有线程的运算全部完成,当前运算才继续执行** 6个同学陆续离开教室后,班长最后离开*/
public class CountDownLatchDemo {public static void main(String[] args) throws InterruptedException {// 总数是6,必须要执行任务的时候,再使用!CountDownLatch countDownLatch = new CountDownLatch(6);for (int i = 1; i <=6 ; i++) {new Thread(()->{System.out.println(Thread.currentThread().getName()+" Go out");countDownLatch.countDown(); // 数量-1},String.valueOf(i)).start();}countDownLatch.await(); // 等待计数器归零,然后再向下执行System.out.println("Close Door");}
}
执行过程:
1.初始化 CountDownLatch
- 创建 CountDownLatch 实例,初始计数器值为 6。
2.启动线程
- 启动 6 个线程,每个线程代表一个同学。
- 每个线程打印自己的名称(表示同学离开教室)。
- 每个线程调用 countDownLatch.countDown() 方法,使计数器减 1。
3.等待计数器归零
- 主线程调用 countDownLatch.await() 方法,阻塞等待直到计数器归零。
- 假设线程 1 到 6 依次执行 countDown() 方法,计数器依次减 1。
- 当计数器归零时,主线程被唤醒,继续执行。
4.打印结果
- 主线程打印 "Close Door",表示班长最后离开。
输出结果:
二、CyclicBarrier:循环集结冲锋号
CyclicBarrier 是 Java 并发工具包(java.util.concurrent)中的一种同步辅助类,用于在多线程环境中实现一组线程互相等待,直到所有线程都到达某个屏障点(barrier point),然后所有线程才能继续执行。
1. CyclicBarrier 的基本概念
定义:CyclicBarrier 是一个同步屏障,允许一组线程互相等待,直到所有线程都到达屏障点。
工作原理:
- 初始化时设置一个计数器,表示需要等待的线程数量。
- 每个线程调用 await() 方法,表示到达屏障点。
- 当所有线程都调用 await() 方法后,屏障点打开,所有线程继续执行。
- CyclicBarrier 可以重复使用,即在所有线程通过屏障点后,计数器会重置,可以再次使用。
代码示例:
package JUC.add;import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierDemo {public static void main(String[] args) {
/*** 集齐7颗龙珠召唤神龙*/// 召唤龙珠的线程CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{System.out.println("召唤神龙成功!");});for (int i = 1; i <=7 ; i++) {final int temp = i;// lambda能操作到 i 吗new Thread(()->{System.out.println(Thread.currentThread().getName()+"收 集"+temp+"个龙珠");try {cyclicBarrier.await(); // 等待} catch (InterruptedException e) {e.printStackTrace();} catch (BrokenBarrierException e) {e.printStackTrace();}}).start();}}
}
代码流程:
1.初始化 CyclicBarrier
- 创建 CyclicBarrier 实例,初始计数器值为 7。
2.启动线程
- 启动 7 个线程,每个线程代表一个收集龙珠的线程。
- 每个线程打印自己的名称和收集的龙珠编号。
- 每个线程调用 cyclicBarrier.await() 方法,表示到达屏障点。
3.等待所有线程到达屏障点
- 主线程启动 7 个子线程后,子线程开始执行。
- 每个子线程调用 cyclicBarrier.await() 方法,计数器减 1。
- 当计数器归零时,所有子线程被唤醒,继续执行。
- 同时,执行 Runnable 任务,打印 "召唤神龙成功!"。
4.继续执行
- 所有子线程继续执行后续代码。
运行结果
4.与CountDownLatch的区别
特性 | CountDownLatch | CyclicBarrier |
---|---|---|
重置能力 | ❌ 一次性使用 | ✅ 可循环使用 |
触发机制 | 外部线程countDown() | 内部线程await() |
典型场景 | 主等从初始化 | 多线程相互等待 |
三、Semaphore(信号量):资源管控红绿灯
Semaphore 是 Java 并发工具包(java.util.concurrent)中的一种同步辅助类,用于控制同时访问特定资源的线程数量。Semaphore 通过维护一组许可(permits)来实现这一功能。
1. Semaphore 的基本概念
定义:Semaphore 是一个计数信号量,用于控制同时访问某一资源的线程数量。
工作原理:
- 初始化时设置一个许可数量(permits),表示可以同时访问资源的线程数量。
- 线程调用 acquire() 方法获取一个许可,如果许可数量大于 0,则线程可以继续执行,并且许可数量减 1。
- 如果许可数量为 0,则线程会被阻塞,直到其他线程释放许可。
- 线程调用 release() 方法释放一个许可,许可数量加 1,如果有线程被阻塞,则其中一个线程会被唤醒并获取许可。
作用: 多个共享资源互斥的使用!并发限流,控制最大的线程数!
代码示例:
package JUC.add;import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;public class SemaphoreDemo {public static void main(String[] args) {// 线程数量:停车位! 限流!Semaphore semaphore = new Semaphore(3);// 3个停车位,设置许可数量for (int i = 1; i <=6 ; i++) {new Thread(()->{// acquire() 得到try {semaphore.acquire();System.out.println(Thread.currentThread().getName()+"抢到车位");// 模拟停车时间TimeUnit.SECONDS.sleep(2);System.out.println(Thread.currentThread().getName()+"离开车位");} catch (InterruptedException e) {e.printStackTrace();} finally {semaphore.release(); // release() 释放该车位}},String.valueOf(i)).start();}}
}
执行流程:
初始化 Semaphore
创建 Semaphore 实例,初始许可数量为 3。
启动线程
启动 6 个线程,每个线程代表一辆汽车。
每个线程调用 semaphore.acquire() 方法获取一个许可。
线程获取许可
假设线程 1、线程 2 和线程 3 成功获取许可,许可数量变为 0。
线程 1、线程 2 和线程 3 打印 "抢到车位",模拟停车时间(休眠 2 秒)。
线程阻塞
线程 4、线程 5 和线程 6 尝试获取许可,但由于许可数量为 0,这些线程会被阻塞。
线程释放许可
假设线程 1 完成停车时间,打印 "离开车位",调用 semaphore.release() 方法释放一个许可,许可数量变为 1。
线程 4 被唤醒,获取许可,打印 "抢到车位",模拟停车时间(休眠 2 秒)。
继续执行
线程 2 和线程 3 完成停车时间,分别打印 "离开车位",调用 semaphore.release() 方法释放许可,许可数量变为 3。
线程 5 和线程 6 被唤醒,分别获取许可,打印 "抢到车位",模拟停车时间(休眠 2 秒)。
所有线程完成
线程 4、线程 5 和线程 6 完成停车时间,分别打印 "离开车位",调用 semaphore.release() 方法释放许可,许可数量变为 3。
输出结果:
🔥 高频面试题破解
Q1:CyclicBarrier和CountDownLatch如何选择?
A:根据"是否需要重复使用"和"等待方向"决定:
-
需要重复使用 → CyclicBarrier
-
主等从 → CountDownLatch
-
从等从 → CyclicBarrier
Q2:Semaphore可以做锁吗?
A:可以!当许可证数设为1时,相当于互斥锁:
Semaphore mutex = new Semaphore(1); // 简易版锁mutex.acquire();
try {// 临界区代码
} finally {mutex.release();
}
💡 总结与升华
三大神器对比表
工具类 | 核心思想 | 典型应用场景 | 重要特性 |
---|---|---|---|
CountDownLatch | 减法计数器 | 主线程等待多个子线程准备 | 一次性、不可重置 |
CyclicBarrier | 加法计数器 | 多线程互相等待触发统一行动 | 可循环使用、支持回调函数 |
Semaphore | 资源许可证管理 | 限流、数据库连接池控制 | 支持公平/非公平模式 |
实战技巧锦囊
-
CountDownLatch常见坑
-
计数器归零后无法重置
-
子线程必须调用countDown()
-
-
CyclicBarrier妙用
-
配合线程池实现分批处理
// 每积累100个任务处理一次 CyclicBarrier barrier = new CyclicBarrier(100, this::batchProcess);
-
-
Semaphore高级玩法
-
实现对象池复用
class ObjectPool<T> {private final List<T> pool;private final Semaphore sem;public ObjectPool(List<T> objects) {pool = new ArrayList<>(objects);sem = new Semaphore(objects.size());}public T borrow() throws InterruptedException {sem.acquire();synchronized (pool) {return pool.remove(0);}} }
-
扩展思考
当我们需要实现以下场景时,该选择哪个工具?
-
大数据分片处理(每处理完1000条数据统一提交)→ CyclicBarrier
-
分布式服务启动检查(所有微服务健康检查通过才开放流量)→ CountDownLatch
-
高并发秒杀系统库存控制 → Semaphore+Redis分布式锁