必须用lock(long position, long size, boolean shared)指定字节范围加锁,position为起始偏移,size为长度(字节),shared为true表示共享锁(允许多读)、false为独占锁(写必需);position+size可超过当前文件长度,支持锁定“未来写入”区域。

FileLock.lock(long, long, boolean) 怎么指定字节范围加锁
锁定文件某一段(比如只锁日志头 1KB,或跳过元数据区域)必须用 lock(long position, long size, boolean shared),不能用无参 lock()。参数含义很直接:position 是起始偏移(从 0 开始),size 是长度(单位字节),shared 决定是共享锁(true,适合多进程并发读)还是独占锁(false,写操作必需)。
常见错误是传错 size:比如想锁最后 1024 字节,却写成 lock(fileSize - 1024, 1024, false),但若 fileSize 是动态增长的,计算时刻和加锁时刻不一致,就可能锁偏;更稳妥的做法是先 channel.size() 获取当前长度,再在 try 块内完成锁 + 写入,避免中间被其他进程截断。
注意:position + size 可以超过当前文件长度——操作系统允许锁一个“未来会写入”的区域,这对预分配日志块、分片写入等场景很有用。
为什么 tryLock(0, 1024, true) 和 tryLock(0, 1024, false) 会互斥
共享锁和独占锁在操作系统层面是严格区分的:tryLock(0, 1024, true) 表示“我只读前 1024 字节”,多个进程可以同时拿到;但只要有一个进程调用了 tryLock(0, 1024, false)(哪怕只写 1 字节),其他所有对该区域的锁请求(无论 shared 是 true 还是 false)都会失败或阻塞。
这决定了你不能混用:比如进程 A 持有 shared 锁读配置头,进程 B 却试图对同一区域加 exclusive 锁写版本号——B 会拿不到锁。所以设计时得明确区域职责:把“只读元数据”和“可写内容区”用不同 offset/size 隔开,避免交叉。
实操建议:
- 用固定偏移划分区域,例如
0–511存校验头,512–存正文,读头用tryLock(0, 512, true),写正文用tryLock(512, Long.MAX_VALUE, false) - 不要对同一
position同时尝试shared和exclusive——这不是竞争问题,是协议冲突 -
tryLock()返回null时,别急着重试;先确认是不是自己或其他进程正持有冲突类型的锁
FileLock 区域锁在容器或 NFS 上为什么经常失效
根本原因是底层文件系统不支持 POSIX fcntl 区域锁语义。Linux 的 ext4 支持,但 overlay2(Docker 默认存储驱动)、NFSv3、CIFS 卷大多只实现全文件锁(flock),忽略 position 和 size 参数——此时 lock(100, 512, false) 实际等效于锁整个文件。
验证方法:df -T /path/to/file 看文件系统类型;或者在容器里运行 strace -e trace=fcntl,flock ls /dev/null 2>&1 | grep -i lock,观察是否真调用了带 offset 的 fcntl(F_SETLK)。
如果部署环境不确定(如 K8s 共享 PVC),就别依赖区域锁。改用全文件锁 + 应用层逻辑分区,或者干脆换分布式协调(如 Redis SET key val NX EX 30 控制写入权)。
release() 必须在 finally 块里显式调用,且要处理 IOException
FileLock.release() 不是自动的,JVM 不管、GC 不清理、进程 crash 后锁也不会释放(除非 OS 回收 fd)。漏掉 release() 的后果很直接:其他进程永远卡在 lock() 或 tryLock(),直到你手动 kill 掉持有锁的 JVM 进程。
正确写法必须带 finally 和异常捕获:
FileLock lock = null;
try {
lock = channel.lock(1024, 2048, false);
if (lock == null) throw new IllegalStateException("Failed to acquire region lock");
// ... write to region
} finally {
if (lock != null && lock.isValid()) {
try {
lock.release();
} catch (IOException e) {
// log but don't throw — release() 失败通常意味着锁已失效或通道关闭
}
}
}容易被忽略的一点:lock.isValid() 返回 false 并不总代表“锁已释放”,它也可能因为通道被关闭、JVM 被 kill -9 而提前失效。所以判断条件里加 lock != null && lock.isValid() 才安全。

















