bigywater.tech
Java · 400

FileChannel.lock:跨进程文件锁的边界

“用文件锁做跨进程互斥”看上去很简单。这篇用两个真实的 JVM 进程量出它的规则和边界:阻塞与非阻塞、共享与区域、同 JVM 与跨进程的差别,以及几个会让两个进程同时进入临界区的陷阱。

1934 ms
A 持有独占锁 2 秒时,B 的 lock() 等待的时间(tryLock 立即返回 null)
isValid = true,但锁已丢
同一进程里关闭同一个文件的另一个通道之后,另一个进程的 tryLock 成功了(macOS)
2 个持有者
锁文件被删除再重建后,A 与 B 都以为自己持有独占锁

跨进程互斥

实测 · 本机执行输出
  B tryLock(独占,整个文件):[14 ms] TRY 独占 [0, ∞) → null(被别的进程持有)
  B lock()(阻塞):[2 ms] WAITING | [1951 ms] ACQUIRED,等了 1934 ms
      B 进程整体 2.0 s;A:[2028 ms] RELEASED
  A 持有共享锁时 B 申请共享:[14 ms] TRY 共享 [0, ∞) → 拿到了
  A 持有共享锁时 B 申请独占:[14 ms] TRY 独占 [0, ∞) → null(被别的进程持有)
  A 独占 [0, 100) 时 B 申请 [100, 200):[14 ms] TRY 独占 [100, 200) → 拿到了
  A 独占 [0, 100) 时 B 申请 [50, 150):[14 ms] TRY 独占 [50, 150) → null(被别的进程持有)

同一个 JVM 内

实测 · 本机执行输出
  同一个 JVM:[4 ms] c1 独占 [0, 100) | [14 ms] c2 独占 [0, 100):OverlappingFileLockException(同一个 JVM 内重叠) | [14 ms] c2 独占 [50, 150):OverlappingFileLockException | [14 ms] c2 独占 [100, 200):拿到了(不重叠的区域) | [15 ms] c2 共享 [0, 100):OverlappingFileLockException | [19 ms] c1 的锁 isValid = true,isShared = false | [20 ms] 释放后 isValid = false

建议性

实测 · 本机执行输出
  B 直接写:[7 ms] WROTE(没有申请锁),文件现在 15 字节
  文件最终内容:无视锁写入

释放的时机与陷阱

实测 · 本机执行输出
  A 持有时 B tryLock:[17 ms] TRY 独占 [0, ∞) → null(被别的进程持有)
  kill -9 A 之后 B tryLock(间隔 0.00 s):[14 ms] TRY 独占 [0, ∞) → 拿到了
  A(同一个进程里关闭了同一个文件的另一个通道):[8 ms] c1 持有独占锁,isValid = true | [13 ms] 关闭同一个文件的另一个通道 c2 之后:c1 的 FileLock.isValid = true(JDK 自己的状态) | [13 ms] HOLDING
  A 还在 HOLDING 时 B tryLock:[14 ms] TRY 独占 [0, ∞) → 拿到了

锁文件被删除

实测 · 本机执行输出
  A:[8 ms] LOCKED 独占 app.lock | [326 ms] DELETED(锁文件被删除)
  B 在 A 删除锁文件之后 tryLock 同一个路径:[17 ms] TRY 独占 [0, ∞) → 拿到了
  A 之后:[1836 ms] A 的锁 isValid = true | [1837 ms] RELEASED

分步演示

FileChannel.lock
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 21.0.12,macOS arm64(本地 APFS 文件系统);进程由 Python 启动并用 stdout 同步
    • 没有验证:Linux(同样是 fcntl,但 NFS 上不同)、Windows(强制锁定,语义与建议性相反)、网络文件系统(NFS / SMB)上的行为、flock 与 fcntl 的差别(JDK 只用 fcntl)、容器里 bind mount 的文件锁、关闭另一个通道丢锁这一点在 Linux 上的行为
    京ICP备2026067502号-1京公网安备11010502063487号