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