bigywater.tech
Java · 169

Semaphore:许可证数量如何控制并发

Semaphore 的许可到底是什么?为什么多 release 一次会出问题?这篇用小实验把许可数、超时、负初始值、所有者和队列顺序一个个量出来(ReentrantLock 的队列见 AQS 一篇,CountDownLatch 与 CyclicBarrier 在已有文章里)。

3 / 433 ms
Semaphore(3) 下 12 个任务的最大并发与总耗时
5
Semaphore(2) 没有 acquire 就 release 3 次后的许可数
没有所有者
任意线程都可以 release

并发上限

实测 · 本机执行输出
  Semaphore(1),12 个任务各占用 100 ms:同时进入临界区的最大数量 = 1,总耗时 1279 ms(12 / 1 × 100 ms ≈ 1200 ms)
  Semaphore(3),12 个任务各占用 100 ms:同时进入临界区的最大数量 = 3,总耗时 433 ms(12 / 3 × 100 ms ≈ 400 ms)
  Semaphore(6),12 个任务各占用 100 ms:同时进入临界区的最大数量 = 6,总耗时 218 ms(12 / 6 × 100 ms ≈ 200 ms)

许可数的算术

实测 · 本机执行输出
  Semaphore(2) 初始 availablePermits = 2
  没有 acquire 就 release 3 次 → availablePermits = 5(许可数可以超过初始值,没有上限)
  tryAcquire(5) = true,之后 availablePermits = 0(一次拿多个许可)
  tryAcquire(1) 在没有许可时 = false;tryAcquire(100 ms) = false(超时返回 false,不抛异常)
  release(3) 后 drainPermits() = 3,之后 availablePermits = 0
  Semaphore(-2):availablePermits = -2,release 2 次后 = 0,tryAcquire() = false,再 release 1 次后 tryAcquire() = 1/true

没有所有者

实测 · 本机执行输出
  Semaphore(0) 当信号用:另一个线程 release 之后 acquire 才返回,等了 160 ms;持有者线程不同(acquire 的线程 ≠ release 的线程):没有「所有者」的概念
  主线程 acquire 之后,别的线程替它 release:availablePermits = 1(没有报错)
  阻塞在 acquire 的线程被 interrupt → InterruptedException,许可数仍是 0

排队顺序

实测 · 本机执行输出
  非公平信号量:主线程持有唯一许可,T1..T5 依次排队;主线程 release 前 hasQueuedThreads = true,queueLength = 5
    获得许可的顺序 = [T1, T2, T3, T4, T5]
  公平信号量:主线程持有唯一许可,T1..T5 依次排队;主线程 release 前 hasQueuedThreads = true,queueLength = 5
    获得许可的顺序 = [T1, T2, T3, T4, T5]

分步演示

Semaphore
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 21.0.12
    • 没有验证:Semaphore 在虚拟线程下的行为、tryAcquire 在公平锁下的插队、许可数溢出(Integer.MAX_VALUE)、与 ReentrantLock + Condition 实现的限流器的比较;耗时是一次运行的结果
    京ICP备2026067502号-1京公网安备11010502063487号