You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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()方法关闭通道,导致锁被悄悄释放。
  • 此时第一个进程仍认为自己持有锁并继续操作,而第二个进程已能成功获取锁创建测试文件,最终引发文件已存在的冲突异常。

解决办法

  1. 保持资源强引用:将RandomAccessFile和FileChannel声明在循环外部,或在持有锁的全程保持对它们的强引用,避免GC回收这些资源。
  2. 用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));
    }
}
  1. 避免锁持有期间触发GC:除非必要,不要在持有锁时调用System.gc(),手动GC会大幅增加资源被回收的概率。

内容的提问来源于stack exchange,提问作者delx

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 19:31:20