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

相关推荐
rannn_1112 小时前
【力扣hot100】链表专题|160、206、234、141、142
java·算法·leetcode·链表·面试·开发
leisoo80973 小时前
100GBA股股票数据怎么存ClickHouseRedisMySQLJSON完整对比
大数据·linux·服务器·开发语言·python
不会代码的小猴3 小时前
21. 泛型编程上
开发语言·c++·笔记·算法
IKUN家族4 小时前
Spring IoC&DI
java·spring·rpc
一只旭宝4 小时前
细讲C加加【9】C++ std::function与std::bind详解|仿函数、绑定器、类成员绑定、占位符、成员偏移指针
开发语言·c++·算法
山荷枝5 小时前
03-框架--Spring
java·后端·spring
ChaHae-In6 小时前
SpringBoot统一功能处理
java·spring boot·后端
coder!mq6 小时前
说几个常见的语法糖?
java·开发语言·算法
gugucoding6 小时前
38. 【Java】Stream API(下):高级操作与性能
java·开发语言
Lhan.zzZ6 小时前
在 Visual Studio 2022 中打造可扩展的动态链接库模块:从零搭建到原理解析
开发语言·c++·visual studio