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 的不同应用。

相关推荐
yuan199972 小时前
基于主成分分析(PCA)的故障诊断MATLAB仿真
开发语言·matlab
J_liaty2 小时前
Java版本演进:从JDK 8到JDK 21的特性革命与对比分析
java·开发语言·jdk
+VX:Fegn08952 小时前
计算机毕业设计|基于springboot + vue律师咨询系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端·课程设计
翔云 OCR API2 小时前
发票查验接口详细接收参数说明-C#语言集成完整示例-API高效财税管理方案
开发语言·c#
daidaidaiyu2 小时前
一文学习和实践 当下互联网安全的基石 - TLS 和 SSL
java·netty
Chasing Aurora2 小时前
Python后端开发之旅(三)
开发语言·python·langchain·protobuf
hssfscv2 小时前
Javaweb学习笔记——后端实战2_部门管理
java·笔记·学习
NE_STOP3 小时前
认识shiro
java
kong79069283 小时前
Java基础-Lambda表达式、Java链式编程
java·开发语言·lambda表达式
liangsheng_g3 小时前
泛型新认知
java·序列化·泛型