Semaphore、CountDownLatch、ReentrantLock使用场景简单说说
目录
Semaphore、CountDownLatch、ReentrantLock使用场景简单说说
1. Semaphore(信号量)
核心思想:控制并发访问资源的线程数量(资源池)
典型场景:
// 1. 数据库连接池(控制最大连接数)
Semaphore semaphore = new Semaphore(10); // 最多10个并发连接
// 2. 限流(接口限流、流量控制)
Semaphore rateLimiter = new Semaphore(100); // QPS限制为100
// 3. 停车场系统(有限停车位)
Semaphore parkingSpots = new Semaphore(50); // 50个车位
关键点:acquire()获取许可,release()释放许可,可指定许可数量。
2. CountDownLatch(倒计时门闩)
核心思想:让一个或多个线程等待其他线程完成操作
典型场景:
// 1. 主线程等待所有子线程初始化完成
CountDownLatch latch = new CountDownLatch(5);
// 每个子线程完成时调用 latch.countDown()
// 主线程调用 latch.await() 等待
// 2. 并行计算,汇总结果
// 3. 模拟并发测试(同时发起请求)
// 4. 游戏等待所有玩家准备就绪
关键点:一次性使用,计数减到0后所有等待线程被唤醒。
3. ReentrantLock(可重入锁)
核心思想:替代synchronized的显式锁,提供更灵活的锁控制
典型场景:
// 1. 需要尝试获取锁(避免死锁)
if (lock.tryLock(3, TimeUnit.SECONDS)) {
try {
// 临界区
} finally {
lock.unlock();
}
}
// 2. 公平锁需求(按申请顺序获取锁)
ReentrantLock fairLock = new ReentrantLock(true);
// 3. 需要条件变量的复杂同步
Condition condition = lock.newCondition();
condition.await(); // 等待条件
condition.signal(); // 唤醒
// 4. 可中断的锁获取
lock.lockInterruptibly();
对比总结
| 工具 | 核心用途 | 是否可重用 | 关键特性 |
|---|---|---|---|
| Semaphore | 控制资源访问并发数 | 是 | 许可机制,可增可减 |
| CountDownLatch | 等待其他线程完成 | 否(一次性) | 倒计时,不可重置 |
| ReentrantLock | 替代synchronized的互斥锁 | 是 | 可重入、可中断、公平/非公平、条件变量 |
简单记忆
- Semaphore:流量控制(多少辆车能上高速)
- 核心思想:控制并发访问资源的线程数量(资源池)
- CountDownLatch:起跑线(等所有运动员就位)
- 核心思想:让一个或多个线程等待其他线程完成操作
- ReentrantLock:加强版synchronized(需要更精细控制时用)
- 核心思想:替代
synchronized的显式锁,提供更灵活的锁控制
- 核心思想:替代
根据具体需求选择:
- 需要限流/资源池 → Semaphore
- 需要等待多个任务完成 → CountDownLatch
- 需要灵活的锁控制 → ReentrantLock
三个并发工具的简单示例
1. Semaphore 示例 - 停车场系统
import java.util.concurrent.Semaphore;
public class ParkingLot {
public static void main(String[] args) {
Semaphore parkingSpots = new Semaphore(3); // 只有3个停车位
// 5辆车尝试停车
for (int i = 1; i <= 5; i++) {
new Thread(() -> {
try {
parkingSpots.acquire(); // 获取停车位
System.out.println(Thread.currentThread().getName() + " 停入车位");
Thread.sleep(2000); // 停车2秒
System.out.println(Thread.currentThread().getName() + " 离开车位");
parkingSpots.release(); // 释放停车位
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "车辆" + i).start();
}
}
}
输出结果:
车辆1 停入车位
车辆2 停入车位
车辆3 停入车位
(等待...)
车辆1 离开车位
车辆4 停入车位
车辆2 离开车位
车辆5 停入车位
...
2. CountDownLatch 示例 - 比赛准备
import java.util.concurrent.CountDownLatch;
public class Race {
public static void main(String[] args) throws InterruptedException {
CountDownLatch readySignal = new CountDownLatch(3); // 3个运动员
System.out.println("裁判:各就各位!");
// 3个运动员线程
for (int i = 1; i <= 3; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " 准备就绪");
readySignal.countDown(); // 计数减1
}, "运动员" + i).start();
}
readySignal.await(); // 裁判等待所有运动员就位
System.out.println("裁判:预备...跑!");
}
}
输出结果:
裁判:各就各位!
运动员1 准备就绪
运动员2 准备就绪
运动员3 准备就绪
裁判:预备...跑!
3. ReentrantLock 示例 - 银行取款
import java.util.concurrent.locks.ReentrantLock;
public class BankAccount {
private int balance = 1000;
private ReentrantLock lock = new ReentrantLock();
// 取款操作
public boolean withdraw(int amount) {
lock.lock(); // 手动加锁
try {
if (balance >= amount) {
Thread.sleep(100); // 模拟处理时间
balance -= amount;
System.out.println(Thread.currentThread().getName() +
" 取款" + amount + ",余额:" + balance);
return true;
}
return false;
} catch (InterruptedException e) {
e.printStackTrace();
return false;
} finally {
lock.unlock(); // 必须在finally中解锁!
}
}
public static void main(String[] args) {
BankAccount account = new BankAccount();
// 两个人同时取款
new Thread(() -> account.withdraw(800), "张三").start();
new Thread(() -> account.withdraw(800), "李四").start();
}
}
输出结果:
张三 取款800,余额:200
李四 取款失败(余额不足)
注意:如果没有锁,可能两人都取款成功,导致余额为负数!
一句话总结
// Semaphore:控制数量(最多N个同时访问)
Semaphore sem = new Semaphore(5); // 最多5个
// CountDownLatch:等待完成(等N个完成后继续)
CountDownLatch latch = new CountDownLatch(3); // 等3个
// ReentrantLock:替代synchronized(手动加锁解锁)
lock.lock(); // 加锁
lock.unlock(); // 解锁
浙公网安备 33010602011771号