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

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

相关推荐
特立独行的猫A3 分钟前
我用仓颉写了个多平台发文工具:一份 Markdown,一条命令发六个平台
java·后端·python
ikgy16 分钟前
JAVA EE:1.计算机是如何工作的
java·java-ee
.痞老板.1 小时前
java ee--计算机是如何工作的
java·开发语言·java-ee
奇树谦1 小时前
Qt Quick 图片加载与 GPU 纹理机制详解:QImage、QQuickTextureFactory、QSGTexture 与异步加载
开发语言·qt·系统架构
青梅味猪大肠1 小时前
【操作系统-60】临近适应算法
java·linux·开发语言·嵌入式硬件·算法·操作系统
CoderYanger1 小时前
Java EE 进阶:8.MyBatis 操作数据库(入门)(第一弹)
java·数据库·spring·面试·职场和发展·java-ee·mybatis
程序员沉梦听雨1 小时前
【搜索引擎】Kibana使用指南
java·后端·学习笔记
starzy19902 小时前
深入理解Scala可变Map:动态构建场景下的性能与设计权衡
开发语言·scala
奶油话梅糖3 小时前
打开项目 0.9 秒、内存只有 IDEA 的 1/12:OpenIDEA 用 Rust 把 IntelliJ IDEA 重造了一遍
java·rust·github·intellij-idea
vipxieliang3 小时前
语言对比卡:PHP vs Go vs Java 的优缺点、适用场景与「为什么选 PHP」
java·go·php