Java FileLock多进程重复加锁问题:锁失效原因咨询
问题场景
在Ubuntu开发、Debian服务器运行的Java程序中,使用FileLock实现跨进程文件锁机制时出现异常:同时运行两个程序实例时,调用System.gc()会导致锁被重复授予,进而触发测试文件已存在的异常。测试覆盖OpenJDK 11和21版本,结果一致。
测试代码
public static void main(String[] args) throws Exception { Random rand = new Random(); while (true) { File lockFile = new File("/tmp/lock-file"); if (!lockFile.exists()) { lockFile.createNewFile(); } RandomAccessFile rac = new RandomAccessFile(lockFile, "rw"); FileChannel channel = rac.getChannel(); FileLock lock = channel.tryLock(); if (lock == null) { System.out.println("Could not get lock"); } else { System.out.println("Got lock"); File testFile = new File("/tmp/test-file"); if (testFile.exists()) { throw new Exception("FILE MUST NOT EXIST. LOCK PROBLEM."); } testFile.createNewFile(); // 触发问题的关键代码 System.gc(); Thread.sleep(rand.nextInt(4)); testFile.delete(); if (!lock.isValid()) { throw new Exception("Lock is not valid anymore!"); } lock.release(); channel.close(); rac.close(); } Thread.sleep(rand.nextInt(4)); } }
实例输出
第一个实例:
java -jar test.jar Got lock Got lock Got lock
第二个实例:
java -jar test.jar Got lock Got lock Could not get lock Got lock Exception in thread "main" java.lang.Exception: FILE MUST NOT EXIST. LOCK PROBLEM. at test.Test.main(Test.java:44)
预期跨进程无法同时获取锁,但实际出现了重复加锁的情况,问题出在哪里?
原因分析
问题核心在于FileLock的生命周期完全依赖于关联的FileChannel,而FileChannel又绑定了RandomAccessFile资源:
- 当
FileChannel被关闭时,对应的FileLock会被自动释放。 - 代码中
RandomAccessFile rac和FileChannel channel是循环内的局部变量,调用System.gc()时,JVM可能将这两个对象判定为可回收(部分GC实现不会追踪lock对它们的间接引用),触发它们的finalize()方法关闭通道,导致锁被悄悄释放。 - 此时第一个进程仍认为自己持有锁并继续操作,而第二个进程已能成功获取锁创建测试文件,最终引发文件已存在的冲突异常。
解决办法
- 保持资源强引用:将
RandomAccessFile和FileChannel声明在循环外部,或在持有锁的全程保持对它们的强引用,避免GC回收这些资源。 - 用try-with-resources管理生命周期:自动确保锁释放后才关闭通道和文件对象,修改示例如下:
public static void main(String[] args) throws Exception { Random rand = new Random(); File lockFile = new File("/tmp/lock-file"); if (!lockFile.exists()) { lockFile.createNewFile(); } while (true) { try (RandomAccessFile rac = new RandomAccessFile(lockFile, "rw"); FileChannel channel = rac.getChannel()) { FileLock lock = channel.tryLock(); if (lock == null) { System.out.println("Could not get lock"); } else { try { System.out.println("Got lock"); File testFile = new File("/tmp/test-file"); if (testFile.exists()) { throw new Exception("FILE MUST NOT EXIST. LOCK PROBLEM."); } testFile.createNewFile(); Thread.sleep(rand.nextInt(4)); testFile.delete(); if (!lock.isValid()) { throw new Exception("Lock is not valid anymore!"); } } finally { lock.release(); } } } Thread.sleep(rand.nextInt(4)); } }
- 避免锁持有期间触发GC:除非必要,不要在持有锁时调用
System.gc(),手动GC会大幅增加资源被回收的概率。
内容的提问来源于stack exchange,提问作者delx
相关产品推荐
相关产品推荐

