Closeable 的幂等性是多线程环境下必须满足的契约,确保并发调用 close() 不引发状态错乱、重复释放或异常;JDK 通过 volatile/AtomicBoolean 标记+首次检查机制实现,自定义类须主动保障原子关闭逻辑。

Closeable 在多线程环境下释放 IO 变量时,幂等性不是可选优化,而是必须满足的契约要求——它保障多个线程并发调用 close() 不会导致状态错乱、重复释放或异常爆炸。
为什么多线程场景下幂等性特别关键
在高并发服务中,IO 资源(如 Socket、FileInputStream、BufferedWriter)常被多个线程共享或竞争关闭。例如:主线程处理完请求准备关闭连接,而超时检测线程同时触发强制关闭;若 close() 非幂等,可能引发:
- 底层文件描述符被重复
close(2)系统调用,某些 OS 返回EBADF并抛出IOException - 缓冲区清空逻辑执行两次,造成数据截断或
NullPointerException - 资源标记位未同步,一个线程认为“已关”,另一个线程仍尝试写入,触发
StreamClosedException
JDK 实现如何保证线程安全的幂等
标准 JDK 流(如 FileInputStream、SocketInputStream)内部普遍采用以下模式:
- 用
volatile boolean closed或AtomicBoolean标记关闭状态,确保可见性 -
close()开头即检查该标记,为 true 则直接返回,不执行任何清理动作 - 所有实际释放操作(如
fd.close()、socket.close())仅在首次进入时执行一次 - 即使底层系统调用本身不幂等(如 Linux 的
close(2)对已关闭 fd 返回-1),上层 Java 层也通过状态判断屏蔽副作用
自定义 Closeable 类的线程安全实践
当你封装流或实现自己的 Closeable 类时,需主动保障多线程幂等:
- 不要依赖底层流是否“自己幂等”——应自行加锁或使用原子状态控制关闭入口
- 推荐用
AtomicBoolean isClosed = new AtomicBoolean(false),if (isClosed.compareAndSet(false, true)) { /* 执行关闭 */ } - 本层清理(如清空本地缓存、注销监听器)和委托关闭(
delegate.close())都必须包裹在同一原子判断内 - 避免在
close()中做耗时同步操作(如远程注销),否则会阻塞其他线程反复重试
与 try-with-resources 的协同要点
虽然 try-with-resources 通常只调用一次 close(),但在复杂嵌套或手动干预场景下仍可能多线程触发:
- 若你在
try块外另起线程调用同一资源的close(),必须依赖其幂等性兜底 - 不要在
close()中抛出非IOException异常——这会破坏try-with-resources的异常抑制机制 - 若委托流关闭失败,应将异常作为 suppressed exception 添加,而非覆盖主异常

















