Java 中的 Semaphore 是 java.util.concurrent
包提供的一个资源访问控制工具类,用于限制同时访问的线程数量,从而实现对共享资源的并发控制。它是基于 AQS(AbstractQueuedSynchronizer) 实现的。
🌟 一、核心概念
1. 信号量(Semaphore)
- 本质上是一个计数器,表示当前可用资源的数量。
- 线程在访问资源前需要先获取信号量(
acquire()
),如果信号量计数大于 0,则允许访问,并将计数减 1。 - 使用完资源后释放信号量(
release()
),计数加 1,唤醒等待线程。
2. 公平性(Fairness)
- 非公平模式(默认):允许线程“插队”尝试获取信号量。
- 公平模式:线程按照等待顺序获取资源(先进先出)。
// 示例:创建一个公平的信号量,初始许可数为3
Semaphore semaphore = new Semaphore(3, true);
🧠 二、核心方法
方法名 | 描述 |
---|---|
acquire() | 获取一个许可,如果没有可用许可则阻塞。 |
acquire(int permits) | 获取指定数量的许可,阻塞直到满足条件。 |
tryAcquire() | 尝试获取一个许可,立即返回是否成功(不阻塞)。 |
tryAcquire(int permits) | 尝试获取指定数量的许可。 |
tryAcquire(long timeout, TimeUnit unit) | 在指定时间内尝试获取许可。 |
release() | 释放一个许可。 |
release(int permits) | 释放指定数量的许可。 |
availablePermits() | 返回当前可用的许可数量。 |
drainPermits() | 获取并返回所有可用许可,剩余为 0。 |
🎯 三、典型使用场景
1. 资源池管理
限制同时访问数据库连接、线程池等资源的数量。
Semaphore pool = new Semaphore(5); // 模拟连接池最多5个连接
pool.acquire(); // 获取连接
// 使用资源...
pool.release(); // 释放连接
2. 限流(Rate Limiting)
控制单位时间内并发请求的数量,防止系统过载。
Semaphore rateLimiter = new Semaphore(10); // 每秒最多处理10个请求
new Thread(() -> {
rateLimiter.acquire();
try {
// 处理请求
} finally {
rateLimiter.release();
}
}).start();
3. 互斥锁(Mutex)
使用许可数为 1 的信号量实现二元互斥锁(类似 ReentrantLock
)。
Semaphore mutex = new Semaphore(1);
mutex.acquire();
try {
// 临界区代码
} finally {
mutex.release();
}
4. 生产者-消费者模型
控制缓冲区的读写操作,避免资源溢出或空读。
🧪 四、与 CountDownLatch / CyclicBarrier 的区别
特性 | Semaphore | CountDownLatch | CyclicBarrier |
---|---|---|---|
用途 | 控制并发访问资源 | 主线程等待多个线程完成 | 多线程互相等待到达屏障点 |
计数方向 | 可增可减(acquire/release) | 递减(countDown) | 递增(await) |
可重用性 | 是 | 否 | 是 |
是否支持超时 | 支持 | 支持 | 支持 |
是否公平 | 支持设置 | 不适用 | 不适用 |
是否可重置 | 是 | 否 | 是 |
🧾 五、示例代码:模拟资源池
import java.util.concurrent.Semaphore;
public class ResourcePool {
private final Semaphore semaphore = new Semaphore(3); // 同时最多3个线程访问
public void useResource() {
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + " 正在使用资源...");
Thread.sleep(2000); // 模拟使用资源
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
semaphore.release();
System.out.println(Thread.currentThread().getName() + " 已释放资源");
}
}
public static void main(String[] args) {
ResourcePool pool = new ResourcePool();
for (int i = 1; i <= 5; i++) {
new Thread(pool::useResource, "Thread-" + i).start();
}
}
}
⚠️ 六、注意事项
- acquire() 会响应中断
- 必须正确处理
InterruptedException
,避免线程卡死。
- 确保 release() 被调用
- 使用
try-finally
块保证资源释放,避免死锁。
- 许可数不能为负数
- 初始化时传入负数会抛出
IllegalArgumentException
。
- 公平性影响性能
- 公平模式虽然避免饥饿,但可能带来性能开销。
- 许可数可大于资源数
- 语义上可以设置许可数大于实际资源数,需开发者自行控制逻辑。
✅ 七、总结
特性 | 说明 |
---|---|
作用 | 控制同时访问的线程数量,实现资源同步和限流 |
底层实现 | 基于 AQS(AbstractQueuedSynchronizer) |
核心方法 | acquire() 、release() 、tryAcquire() |
典型场景 | 资源池、限流、互斥锁、生产者消费者模型 |
与其他工具区别 | 与 CountDownLatch 和 CyclicBarrier 各有适用场景 |
注意事项 | 处理中断、确保释放、合理设置许可数和公平性 |
THE END