FileLock是建议性锁,仅在所有进程主动配合时生效;必须统一用FileChannel.lock()加锁、禁止绕过通道写入、按字段分区提升并发、避免tryLock()用于关键配置更新,并在跨语言或容器环境中改用外部协调机制。

Java 的 FileLock 本身不能“自动防止”多进程破坏数据,它只在所有参与者都主动配合的前提下起作用——也就是说,它不是强制锁,而是建议性锁(advisory lock)。真正起保护作用的,是你统一的读写流程和对锁的严格遵守。
必须用 FileChannel + lock(),不能绕过
只要有一个进程跳过锁机制,比如用 FileOutputStream、Files.write() 或 Shell 重定向直接写文件,其他进程加的锁就完全失效。所以关键不是“怎么加锁”,而是“所有写操作是否都走同一条受控路径”:
- 打开文件必须用
RandomAccessFile或FileChannel.open(),且带读写权限(如"rw") - 写入前必须调用
channel.lock(0, Long.MAX_VALUE, false)(全文件独占)或指定区域的lock(pos, size, false) - 所有修改必须在锁持有期间完成,写完立即
force(true)刷盘,再释放锁 - 禁止使用
FileWriter、PrintWriter、Files.write()等跳过通道的 API
按字段分区加锁,避免全局阻塞
如果文件里有多个逻辑部分(比如配置头 + 版本号 + 主数据),可以分区域加锁,提升并发度:
- 校验头(0–63 字节):用
tryLock(0, 64, true)允许多进程并发读 - 版本号字段(64–71 字节):用
lock(64, 8, false)独占更新 - 主数据区(72 字节起):单独加锁写入,不影响元数据操作
- 注意:同一字节区间不能同时存在 shared 和 exclusive 锁,否则后者会失败
别依赖 tryLock() 做关键配置更新
tryLock() 是非阻塞的,返回 null 只表示“此刻没抢到”,不说明谁占着、多久释放。用于日志追加还行,但配置更新等强一致性场景容易出问题:
立即学习“Java免费学习笔记(深入)”;
- 反复轮询空转 CPU,对方崩溃未释放锁就永远卡住
- 业务误判为“无冲突”而降级写入,导致配置不一致
- 推荐做法:用
tryLock()配合带超时和重试上限的循环(比如最多 3 次,每次间隔 100ms) - 更稳妥的方案是改用阻塞式
lock(),并设置线程中断或超时控制(例如结合Future或ExecutorService)
跨语言/跨工具时要额外约定
Java 进程加了锁,Python 脚本、Shell 脚本、运维工具照样能直接写文件——因为它们没调 flock() 或 fcntl.flock()。所以:
- 如果是混合技术栈,必须统一约定加锁协议(比如所有脚本都调用
flock -x) - 否则应考虑更可靠的外部协调机制:Redis 分布式锁、数据库行锁、ZooKeeper 临时节点,或基于原子 rename 的文件交换方案
- 特别注意容器环境(Docker 默认 overlay2)、NFS 卷,这些常不支持区域锁,
lock(100, 512, false)实际可能锁整个文件


















