filelock 能防止多 jvm 进程同时写同一文件,但仅在操作系统层面生效,且必须通过 filechannel 的 trylock() 或 lock() 实现;randomaccessfile 或 fileoutputstream 自行加锁无效。

FileLock 能不能防止多 JVM 进程同时写同一个文件
能,但只在操作系统层面生效,且必须用 FileChannel 配合 tryLock() 或 lock() 才真正起作用。用 RandomAccessFile 或 FileOutputStream 自己加锁、改文件名、写标志位——这些全都不算数,OS 看不见。
常见错误现象:IOException: No locks available(NFS 挂载点或容器里没启用 flock)、OverlappingFileLockException(同一 JVM 内重复 lock 同一 channel)、看似加锁成功但另一进程仍能写入(用了 new FileOutputStream(...) 却没走 getChannel().lock())。
- 必须通过
FileChannel获取锁,FileInputStream.getChannel()返回的 channel 默认不可写,得用RandomAccessFile("...", "rw").getChannel()或FileOutputStream.getChannel() - Linux/macOS 用的是 advisory lock(建议性锁),依赖所有参与者主动检查;Windows 是 mandatory lock(强制性),但仅对映射到该句柄的 I/O 生效
- 锁绑定的是「打开的文件描述符」,不是文件路径——两个进程打开同一文件,各自拿到不同 fd,
lock()才会互斥
FileLock 在容器或 Docker 中为什么经常失效
根本原因是底层存储驱动不支持 POSIX 文件锁,尤其是 overlay2 + rootless 容器、或挂载了 NFS/CIFS 的卷。
使用场景:Spring Boot 多实例部署在 K8s,共享配置目录下写运行时 PID 文件;或批处理任务集群争抢一个输入文件。
立即学习“Java免费学习笔记(深入)”;
- 检查是否在挂载卷上用锁:
df -T /path/to/file,若显示nfs、cifs、overlay,基本不可靠 - Docker 默认禁用
CAP_SYS_ADMIN,而某些文件系统锁实现需要它;加--cap-add=SYS_ADMIN也不一定能救,因为 overlay2 本身不传递 flock - 替代方案优先考虑外部协调服务(如 Redis 的
SET key val NX EX 30),而不是硬扛FileLock
tryLock() 和 lock() 的实际区别不只是“是否阻塞”
tryLock() 立即返回 null 或锁对象,lock() 会一直等——但更关键的是:前者是非阻塞语义,后者在中断线程时可能抛 IOException,且两者都受底层文件系统信号处理影响。
性能影响:在高争用场景下,反复 tryLock() + sleep 会浪费 CPU;而 lock() 看似省事,但若持有锁的进程崩溃未释放,等待方可能卡死(Java 不自动清理 native 锁)。
-
tryLock()成功后必须手动release(),否则锁一直占着;JVM crash 或 kill -9 会导致锁残留,需靠 OS 超时或重启清理 -
lock()可被Thread.interrupt()中断,但部分 Linux 内核版本会把中断转为IOException: Bad file descriptor,不是InterruptedException - 不要在 finally 块里无条件
release()——如果tryLock()返回 null,调用release()会 NPE
FileLock 释放时机和 JVM 生命周期的关系
锁在对应的 FileChannel 关闭时自动释放,不是 GC 回收时,也不是 JVM 退出时自动清理。这意味着:显式 close、channel 所属流关闭、或 JVM crash,都会导致锁立即释放(OS 层面)。
容易踩的坑:用 try-with-resources 包住 RandomAccessFile,但忘了它的 getChannel() 是共享引用——关掉 RAF 并不等于关掉 channel;或者用 Files.newByteChannel() 得到 channel,却没关。
-
FileChannel是可继承的,子进程不会自动继承父进程的锁;但 fork 出的子 JVM 如果复用同一 fd(如用exec启动),锁状态会被继承 - JVM 正常 shutdown hook 中释放锁是可行的,但 kill -9 或 OOM 直接杀进程,锁由 OS 在 fd 关闭时清理,时间点不可控
- 调试时用
lsof -i | grep yourfile(Linux)或lsof -nP +L1查当前谁持锁,比看 Java 日志更准
真正难的不是怎么加锁,而是确认锁在哪一层生效、谁能看到它、以及当它“看起来没用”时,到底是代码没走对路,还是环境根本不支持。别迷信 API 文档里的“跨进程互斥”,先跑个双 JVM 小实验,再决定要不要把它放进生产逻辑里。










