Java信号量Semaphore:并发控制的利器

一、核心概念

1. 什么是信号量?

  • 信号量维护一组"许可证"(permits),线程在访问受保护资源前必须先获取一个许可证。
  • 如果没有可用许可证,线程会阻塞(或立即返回失败,取决于调用方法),直到有其他线程释放许可证。
  • 不实际创建"许可证对象",只是用一个整数计数器表示当前可用数量。

比如:停车场有 10 个车位(permits = 10)。每来一辆车(线程),要先"acquire"一个车位;离开时"release"归还车位。

2. 两种模式

  • 非公平(Nonfair):默认模式。新来的线程可能插队获取许可证,即使有线程在等待("barging"行为),吞吐量高但可能导致饥饿。
  • 公平(Fair):按 FIFO 顺序分配许可证,避免线程长时间等待,但性能略低。

3. 特殊用法:二元信号量(Binary Semaphore)

  • 当 permits = 1 时,信号量退化为互斥锁(Mutex)。
  • 关键区别 :普通锁(如 ReentrantLock)要求"谁加锁谁解锁",而信号量不要求所有权 ------任何线程都可以调用 release()。这在死锁恢复等特殊场景有用。

二、关键实现机制(基于 AQS)

Semaphore 内部使用 AbstractQueuedSynchronizer(AQS)实现同步逻辑:

java 复制代码
private final Sync sync; // Sync 是 AQS 的子类

Sync 的两个子类:

  • NonfairSync:非公平版本,tryAcquireShared 直接尝试 CAS 减少许可数。
  • FairSync:公平版本,在尝试获取前先检查是否有排队线程(hasQueuedPredecessors()),如果有就直接返回 -1(表示需排队)。

核心方法:

方法 作用
acquire() 获取 1 个许可,不可用则阻塞(可中断)
release() 释放 1 个许可,唤醒等待线程
tryAcquire() 尝试获取,立即返回 true/false
availablePermits() 返回当前可用许可数(调试用)
drainPermits() 一次性拿走所有可用许可

所有操作底层都通过 AQS 的 state 字段(int 类型)表示许可数量,使用 CAS 保证原子性。


三、典型使用场景

场景 1:限制并发访问数量

java 复制代码
// 最多允许 3 个线程同时访问数据库连接池
Semaphore dbPool = new Semaphore(3, true); // 公平模式

public void accessDB() throws InterruptedException {
    dbPool.acquire();      // 获取许可
    try {
        // 使用数据库连接
    } finally {
        dbPool.release();  // 归还许可
    }
}

场景 2:资源池管理(如你代码中的 Pool 示例)

  • 控制对有限资源(如对象池、文件句柄)的访问。
  • getItem() 前 acquire,putItem() 后 release。

场景 3:流量控制 / 限流

  • 比如 Web 服务器限制每秒最多处理 100 个请求,可用信号量控制。

四、重要注意事项

  1. 许可数可以为负数

    构造时传入负数,表示"欠债",必须先 release() 多次才能让 acquire() 成功。

  2. tryAcquire() 不遵守公平策略

    即使设为公平模式,tryAcquire() 仍会"插队"获取可用许可。

  3. 内存可见性保证
    release() 之前的动作 happen-before acquire() 之后的动作,确保线程间数据可见。

  4. 不要求"配对"使用

    A 线程 acquire,B 线程 release 是合法的(虽然通常不推荐,容易出错)。


五、总结一句话

Semaphore 是一个计数型的同步工具,通过控制"许可证"的发放与回收,限制同时访问某资源的线程数量,既可用于资源池管理,也可作为更灵活的锁机制。

如果你正在学习并发编程,建议结合 ReentrantLockCountDownLatchCyclicBarrier 对比理解,它们都是基于 AQS 的不同应用。

相关推荐
不愿是过客21 小时前
java实战干货——长方法深递归
java
u01092727121 小时前
C++中的策略模式变体
开发语言·c++·算法
雨季6661 天前
构建 OpenHarmony 简易文字行数统计器:用字符串分割实现纯文本结构感知
开发语言·前端·javascript·flutter·ui·dart
雨季6661 天前
Flutter 三端应用实战:OpenHarmony 简易倒序文本查看器开发指南
开发语言·javascript·flutter·ui
小北方城市网1 天前
Redis 分布式锁高可用实现:从原理到生产级落地
java·前端·javascript·spring boot·redis·分布式·wpf
进击的小头1 天前
行为型模式:策略模式的C语言实战指南
c语言·开发语言·策略模式
天马37981 天前
Canvas 倾斜矩形绘制波浪效果
开发语言·前端·javascript
六义义1 天前
java基础十二
java·数据结构·算法
Tansmjs1 天前
C++与GPU计算(CUDA)
开发语言·c++·算法
qx091 天前
esm模块与commonjs模块相互调用的方法
开发语言·前端·javascript