semaphore异常安全关键在于开发者必须显式保障许可释放,jvm不会自动补救遗漏的release();许可滞留会导致资源耗尽型饥饿,oom时线程在acquire后崩溃而未release是主因,正确做法是将release置于finally块中。

Java 中 Semaphore 的异常安全关键在于:许可释放必须由开发者显式保障,JVM 不会自动补救遗漏的 release()。它本身不“卡住”,但许可一旦滞留,就会造成资源耗尽型饥饿——可用许可数永久减少,其他线程无限等待。
OOM 场景下许可滞留的真实原因
OutOfMemoryError 并非让 Semaphore 失效,而是打断线程执行流。真正危险的是:线程在 acquire() 成功返回后、尚未执行 release() 前就崩溃。此时许可已被扣减,但无人归还。
- 常见诱因:在
acquire()后立即分配大对象(如new byte[100MB]),触发堆溢出 - 误用
tryAcquire():超时返回false却当作成功处理,后续仍调用release()或漏掉该调用 - 多层方法嵌套中,某一层未将
release()放入finally,异常向上穿透导致释放逻辑被跳过
必须写进 finally 的 release 逻辑
release() 不是可选操作,而是与 acquire() 严格配对的义务。不能依赖 JVM 或 AQS 自动兜底——AQS 只保证自身状态变更原子性,不保证业务线程一定走到释放语句。
Java JDK 25 来自 OpenJDK 官方归档,版本为 JDK 25,本条下载地址已指向官方 Windows x64 zip 安装包直链,适合调试旧项目或兼容旧版 Java 运行环境。
- 正确写法:
try { semaphore.acquire(); /* 业务逻辑 */ } finally { semaphore.release(); } - 禁止写法:
semaphore.acquire(); /* 业务逻辑 */ semaphore.release(); // 无 try-finally,任何异常都会跳过 release - 若需响应中断,
acquire()的异常需在 catch 中重置中断状态,再抛出或处理,但finally中的release()仍不可省略
增强资源管理的实践方式
把许可获取与释放下沉为资源生命周期的一部分,降低人为疏漏概率。
- 对关键资源(如连接、文件句柄),封装成
AutoCloseable实现,在close()中执行release(),配合try-with-resources自动收尾 - 避免在
synchronized块内调用acquire():双重同步易掩盖许可归属,OOM 时更难定位哪段代码持有了许可 - 上线前做破坏性验证:用
-XX:+CrashOnOutOfMemoryError配合压力测试,观察许可是否能稳定回收;监控availablePermits()和getQueueLength()指标变化
公平性与异常安全无关,但影响问题暴露速度
公平模式(new Semaphore(permits, true))会让等待线程严格排队,一旦许可滞留,后续所有线程都会阻塞在队首,问题立刻显现;非公平模式下,新线程可能插队成功,掩盖许可短缺,延迟暴露泄漏问题——但这不是安全差异,只是表象不同。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










