Java: 如何在文件锁定时阻止 read/writing 文件

Java: How to hold off read/writing a file while it's locked

我有许多单独的应用程序(全部用 Java 编写)需要在短时间内访问文件。其中一些进程需要读取权限,而其他进程则需要更新文件。为了让所有这些应用程序都能很好地运行,我想编写一些代码,使用 OS 锁在每个应用程序需要时获得对文件的独占访问权限。

执行此操作的明显方法 RandomAccessFile myFile = new RandomAccessFile(file, "rw"),但是如果另一个进程已经拥有锁,这将失败。我需要的是后退并重试的能力。

我希望编写一些使用 channel.tryLock() 的代码来测试是否已取出锁。问题是我需要一个通道,但我似乎无法在不取出锁的情况下获得该通道对象!

更新

我需要找到一种方法来检查文件是否被锁定。我想在不抛出异常的情况下执行此操作。

我的代码的简化版本是:

void myMethod(File myFile) {
    try (
        RandomAccessFile myFile = new RandomAccessFile(myFile, "rw");  // Exception here
        FileChannel myChannel = myFile.getChannel();
        FileLock myLock = lockFile(myChannel )
    ) {
        // Stuff to read and/or write the file
    }
}

private FileLock lockFile(FileChannel channel) throws Exception {
    FileLock lock;

    while (lock = channel.tryLock() == null) {
        Thread.sleep(100);
    }

    return lock;
}

问题是,如果文件被锁定,它会在突出显示的行上失败(通过抛出异常)- 在可以获得文件锁定的点之前。

获取频道的其他变体如FileChannel channel = FileChannel.open(myFile.toPath(), StandardOpenOption.WRITE, StandardOpenOption.APPEND)也会抛出异常:

Exception in thread "main" java.nio.file.FileSystemException: path\to\my\file.txt: The process cannot access the file because it is being used by another process.

那么我怎样才能获得一个通道来测试锁而不抛出异常呢?

使用 FileLock 的标准方法是打开文件,例如通过 FileChannel.open,然后是 tryLock。锁的存在不会阻止其他进程打开文件。

这可以通过以下程序证明:

import java.io.IOException;
import java.nio.channels.*;
import java.nio.file.*;

class Locking {
    public static void main(String[] args) throws IOException, InterruptedException {
        if(args.length > 0) {
            String me = String.format("%6s ", ProcessHandle.current());
            Path p = Paths.get(args[0]);
            try(FileChannel fc = FileChannel.open(p,
                    StandardOpenOption.WRITE, StandardOpenOption.APPEND)) {

                FileLock l = fc.tryLock();
                if(l == null) System.out.println(me + "could not acquire lock");
                else {
                    System.out.println(me + "got lock");
                    Thread.sleep(3000);
                    System.out.println(me + "releasing lock");
                    l.release();
                }
            }
        }
        else {
            Path p = Files.createTempFile("lock", "test");
            String[] command = {
                Paths.get(System.getProperty("java.home"), "bin", "java").toString(),
                "-cp", System.getProperty("java.class.path"),
                "Locking", p.toString()
            };
            ProcessBuilder b = new ProcessBuilder(command).inheritIO();
            Process p1 = b.start(), p2 = b.start(), p3 = b.start();
            p1.waitFor();
            p2.waitFor();
            p3.waitFor();
            Files.delete(p);
        }
    }
}

打印出类似的东西

 12116 got lock
 13948 could not acquire lock
 13384 could not acquire lock
 12116 releasing lock

可以是demonstrated online on tio.run

虽然此程序在 Windows 下工作相同,但此操作系统支持打开未共享的文件,防止其他进程打开。如果不同的进程以这种方式打开文件,我们甚至无法打开它来探测锁定状态。

这不是打开文件的方式,Java,但是,有一个非标准的打开选项可以复制该行为,com.sun.nio.file.ExtendedOpenOption.NOSHARE_WRITE。在最近的 JDK 中,它位于 jdk.unsupported 模块中。

当我们运行下面扩展测试程序时Windows

import java.io.IOException;
import java.nio.channels.*;
import java.nio.file.*;
import java.util.HashSet;
import java.util.Set;

class LockingWindows {
    public static void main(String[] args) throws IOException, InterruptedException {
        if(args.length > 0) {
            String me = String.format("%6s ", ProcessHandle.current());
            Path p = Paths.get(args[0]);
            Set<OpenOption> options
                = Set.of(StandardOpenOption.WRITE, StandardOpenOption.APPEND);
            if(Boolean.parseBoolean(args[1])) options = addExclusive(options);
            try(FileChannel fc = FileChannel.open(p, options)) {
                FileLock l = fc.tryLock();
                if(l == null) System.out.println(me + "could not acquire lock");
                else {
                    System.out.println(me + "got lock");
                    Thread.sleep(3000);
                    System.out.println(me + "releasing lock");
                    l.release();
                }
            }
        }
        else {
            Path p = Files.createTempFile("lock", "test");
            String[] command = {
                Paths.get(System.getProperty("java.home"), "bin", "java").toString(),
                "-cp", System.getProperty("java.class.path"),
                "LockingWindows", p.toString(), "false"
            };
            ProcessBuilder b = new ProcessBuilder(command).inheritIO();
            for(int run = 0; run < 2; run++) {
                Process p1 = b.start(), p2 = b.start(), p3 = b.start();
                p1.waitFor();
                p2.waitFor();
                p3.waitFor();
                if(run == 0) {
                    command[command.length - 1] = "true";
                    b.command(command);
                    System.out.println("\nNow with exclusive mode");
                }
            }
            Files.delete(p);
        }
    }

    private static Set<OpenOption> addExclusive(Set<OpenOption> options) {
        OpenOption o;
        try {
            o = (OpenOption) Class.forName("com.sun.nio.file.ExtendedOpenOption")
                .getField("NOSHARE_WRITE").get(null);
            options = new HashSet<>(options);
            options.add(o);
        } catch(ReflectiveOperationException | ClassCastException ex) {
            System.err.println("opening exclusive not supported");
        }
        return options;
    }
}

我们会得到类似

的东西
  2356 got lock
  6412 could not acquire lock
  9824 could not acquire lock
  2356 releasing lock

Now with exclusive mode
  9160 got lock
Exception in thread "main" java.nio.file.FileSystemException: C:\Users\...\Temp\lock13936982436235244405test: The process cannot access the file because it is being used by another process
    at java.base/sun.nio.fs.WindowsException.translateToIOException(WindowsException.java:92)
    at java.base/sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:103)
    at java.base/sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:108)
    at java.base/sun.nio.fs.WindowsFileSystemProvider.newFileChannel(WindowsFileSystemProvider.java:121)
    at java.base/java.nio.channels.FileChannel.open(FileChannel.java:298)
    at LockingWindows.main(LockingWindows.java:148)
Exception in thread "main" java.nio.file.FileSystemException: C:\Users\...\Temp\lock13936982436235244405test: The process cannot access the file because it is being used by another process
    at java.base/sun.nio.fs.WindowsException.translateToIOException(WindowsException.java:92)
    at java.base/sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:103)
    at java.base/sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:108)
    at java.base/sun.nio.fs.WindowsFileSystemProvider.newFileChannel(WindowsFileSystemProvider.java:121)
    at java.base/java.nio.channels.FileChannel.open(FileChannel.java:298)
    at LockingWindows.main(LockingWindows.java:148)
  9160 releasing lock

与您测试结果的相似性表明 Windows 您 运行 与 Java 程序同时使用的程序确实使用了这种模式。

对于您的 Java 程序,只要您不使用该模式,就不会出现此类问题。仅当您必须与另一个未使用协作锁定的 Windows 程序交互时,您才需要处理此问题。