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

相关推荐
玄同76513 小时前
从 0 到 1:用 Python 开发 MCP 工具,让 AI 智能体拥有 “超能力”
开发语言·人工智能·python·agent·ai编程·mcp·trae
czy878747513 小时前
深入了解 C++ 中的 `std::bind` 函数
开发语言·c++
消失的旧时光-194313 小时前
从 Kotlin 到 Dart:为什么 sealed 是处理「多种返回结果」的最佳方式?
android·开发语言·flutter·架构·kotlin·sealed
yq19820430115613 小时前
静思书屋:基于Java Web技术栈构建高性能图书信息平台实践
java·开发语言·前端
一个public的class13 小时前
你在浏览器输入一个网址,到底发生了什么?
java·开发语言·javascript
有位神秘人13 小时前
kotlin与Java中的单例模式总结
java·单例模式·kotlin
Jinkxs13 小时前
Gradle - 与Groovy/Kotlin DSL对比 构建脚本语言选择指南
android·开发语言·kotlin
&有梦想的咸鱼&13 小时前
Kotlin委托机制的底层实现深度解析(74)
android·开发语言·kotlin
golang学习记13 小时前
IntelliJ IDEA 2025.3 重磅发布:K2 模式全面接管 Kotlin —— 告别 K1,性能飙升 40%!
java·kotlin·intellij-idea
爬山算法13 小时前
Hibernate(89)如何在压力测试中使用Hibernate?
java·压力测试·hibernate