Java Semaphore 原理与实战解析

参考文献:
[2] 官方文档:Java Semaphore API
[3] 源码参考:JDK Semaphore 源码


一、概述与名词解释

1.1 技术背景

在多线程并发编程中,资源访问冲突是常见问题。Java 并发包(java.util.concurrent)提供了多种同步工具,其中 Semaphore(信号量) 是一种经典的限流与资源计数器方案。早在 1965 年 Edsger Dijkstra 就提出了信号量机制,用于操作系统进程协作,随后成为操作系统和分布式系统中的基础同步原语。

1.2 相关术语

  • Semaphore(信号量):一种用于控制同时访问特定资源线程数量的同步工具。
  • Permit(许可):信号量内部的计数,每个线程获取/释放资源时会增减许可数量。
  • AQS(AbstractQueuedSynchronizer):JDK 并发包的核心同步队列实现,Semaphore 基于 AQS 实现。
  • 公平/非公平锁:公平锁保证按照请求顺序分配许可,非公平锁可能插队。

1.3 应用场景

  • 连接池限流
  • 限制接口并发
  • 控制资源访问(如数据库、文件等)

二、主流程分解

Semaphore 的核心流程包括 初始化获取许可(acquire)释放许可(release) 三大环节。

2.1 初始化

设计思想:通过构造器设置最大许可数,底层将许可数赋值到 AQS 的 state 字段。

技巧与优缺点

  • 优点:灵活控制并发度,支持公平/非公平模式。
  • 缺点:许可数一旦设置,动态调整需重建对象。

业务场景举例
如限制同时最多2个线程访问某个资源:

Semaphore semaphore = new Semaphore(2);

2.2 获取许可

设计思想:线程尝试原子性获取许可,许可不足则挂起线程并加入同步队列。

流程简述

  1. 检查 state 是否足够,state >= 1 则减1并返回。
  2. state < 1 时,构造 Node 节点加入阻塞队列,挂起线程。
  3. 唤醒后重试,直到获取成功。

优缺点

  • 优点:避免资源争抢,线程安全。
  • 缺点:可能发生线程饥饿(非公平模式)。

业务场景举例
API限流,每秒只允许2个请求并发处理。

2.3 释放许可

设计思想:释放许可时增加 state,并唤醒阻塞队列中等待的线程。

流程简述

  1. state 加1。
  2. 唤醒阻塞队列所有共享节点。

优缺点

  • 优点:高效唤醒,保证并发性。
  • 缺点:唤醒所有节点可能导致“惊群效应”。

业务场景举例
数据库连接归还后,唤醒等待连接的线程。


三、流程图与源码解析

3.1 流程图(flowchart)

成功
失败
初始化 Semaphore
获取许可
执行任务
加入阻塞队列
被唤醒后重试
释放许可
唤醒阻塞队列线程

3.2 状态图(stateDiagram-v2)

acquire成功
acquire失败
被唤醒重试
release
初始化
等待许可
执行中
阻塞等待
释放许可

3.3 时序图(sequenceDiagram)

Thread Semaphore Queue acquire() 获取成功 加入阻塞队列 挂起 唤醒 重试acquire alt [许可充足] [许可不足] release() 唤醒队列线程 Thread Semaphore Queue

3.4 核心源码与速记口诀

关键源码目录
public class Semaphore {
    private final Sync sync; // AQS同步器

    public Semaphore(int permits) {
        sync = new NonfairSync(permits);
    }

    public void acquire() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }

    public void release() {
        sync.releaseShared(1);
    }
}
acquire源码注释
public void acquire() throws InterruptedException {
    // 1. 尝试原子性获取许可
    sync.acquireSharedInterruptibly(1);
}

口诀
初始化定许可,获取原子减,失败入队挂,释放加一唤。


四、调试与优化技巧

4.1 调试技巧

  • 利用 JDK VisualVM 或 JMC 监控阻塞队列长度、线程状态。
  • 打印 AQS state 变化,定位许可分配异常。
  • 仔细检查 acquire/release 对应关系,防止“死锁”或“泄漏”。

4.2 性能优化

  • 使用非公平模式提升吞吐量(但可能导致部分线程饥饿)。
  • 合理设置许可数,避免过度唤醒。
  • 与 ThreadPoolExecutor 结合,限流任务提交。

4.3 技术栈集成

  • 与 Redis、ZooKeeper 实现分布式信号量(Redisson Semaphore)。
  • Spring Boot 提供 @Async + Semaphore 控制接口并发。

4.4 高阶应用

  • 自定义 Semaphore 子类,实现动态许可调整。
  • 支持限流降级、熔断等扩展。

五、底层实现与架构演进

5.1 底层机制

  • 核心依赖 AQS 的 state 字段和同步队列(CLH队列)。
  • 许可获取/释放均为 CAS 原子操作,保证线程安全。
  • 节点唤醒采用 LockSupport.park/unpark 实现线程挂起/唤醒。

5.2 架构演进

  • JDK 早期采用 synchronized + wait/notify 实现,性能较差。
  • JDK5+ 引入 AQS,统一同步机制,提升并发性能。
  • 现代分布式系统采用 Redis/ZK 等实现跨进程信号量。

六、全文总结

Semaphore 是 Java 并发编程中的重要同步工具,适用于限流、资源池等场景。其底层基于 AQS,同步队列和原子操作保证线程安全。理解信号量的原理、流程和源码,有助于开发高效可靠的并发系统。调试和优化 Semaphore 需关注许可数设置、阻塞队列管理及与其他技术栈的集成。随着架构演进,信号量已扩展至分布式系统领域,成为现代限流方案的重要基础。

知其然,更知其所以然,Semaphore是并发限流的基石。


欢迎交流讨论,更多并发编程内容敬请关注!

更多推荐