semaphore核心语义是许可获取与释放可由不同线程完成,基于aqs的无owner原子计数器实现,不校验线程身份,支持异步回调、超时清理等真实场景。

Semaphore 的核心语义之一,正是“获取与释放可由不同线程完成”——它不绑定线程身份,只维护一个原子计数器。这种解耦不是“奇特”,而是设计本意,也是其区别于 ReentrantLock(必须同线程加锁/解锁)的关键特征。下面用 Java 原生代码直接证明这一点。
用最简 Java 示例验证跨线程 release
以下代码中:
- 主线程(A)调用
acquire()获取许可后,不释放; - 另起一个独立子线程(B),在延迟后调用
release(); - 再由第三个线程(C)成功
acquire(),证明许可已被 B 正确归还。
全程无任何异常,且输出顺序清晰可验证:
import java.util.concurrent.Semaphore;
public class SemaphoreCrossThreadRelease {
public static void main(String[] args) throws InterruptedException {
Semaphore sem = new Semaphore(1); // 初始 1 个许可
// A 线程:获取许可
System.out.println("A: acquiring...");
sem.acquire(); // 计数器 → 0
System.out.println("A: acquired, now count = " + sem.availablePermits());
// B 线程:异步释放(非 A 线程)
Thread bThread = new Thread(() -> {
try {
Thread.sleep(300);
System.out.println("B: releasing...");
sem.release(); // 计数器 → 1
System.out.println("B: released, now count = " + sem.availablePermits());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}, "B-Thread");
bThread.start();
// C 线程:等待并尝试获取(验证 B 是否生效)
Thread cThread = new Thread(() -> {
try {
System.out.println("C: waiting to acquire...");
sem.acquire(); // 此处将被阻塞,直到 B release 后才返回
System.out.println("C: acquired successfully — proof of cross-thread release!");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}, "C-Thread");
cThread.start();
cThread.join();
bThread.join();
}
}
为什么这能成立?看底层机制
Java Semaphore 基于 AQS(AbstractQueuedSynchronizer),其状态(state)是纯数值、线程无关的:
-
acquire()→ 对state执行 CAS 减操作; -
release()→ 对state执行 CAS 加操作; - 所有操作都通过
Unsafe.compareAndSetInt保证原子性,不记录调用线程 ID; - 唤醒逻辑只依赖队列中等待节点,与谁释放无关。
对比 ReentrantLock 就更清楚
若换成 ReentrantLock:
- A 线程
lock()后,B 线程调用unlock()会抛IllegalMonitorStateException; - 因为
ReentrantLock内部持有exclusiveOwnerThread字段,强制归属校验; - 而
Semaphore没有 owner 概念,只有state和 FIFO 等待队列。
实际意义不止是“能用”,而是刚需
这种解耦支撑了真实场景:
- 异步 I/O 回调释放资源:主线程发请求 → Netty EventLoop 收到响应 → 在回调中 release;
- 超时自动清理:A 获取许可后启动定时任务,到期由 TimerThread 调用 release;
- 资源池归还:连接从线程 T1 借出,由连接检测线程 T2 在空闲时归还到池中。










