【问题标题】:Java: How to hold off read/writing a file while it's lockedJava:如何在文件锁定时推迟读/写文件
【发布时间】:2021-08-03 20:25:22
【问题描述】:

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

这样做的明显方法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.

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

【问题讨论】:

  • 也许您应该详细说明“我似乎无法在不取出锁的情况下获取该通道对象”。您尝试了什么,结果如何,等等。打开频道并调用tryLock() 正是它应该如何工作(还有什么?)...
  • 使用FileChannel.open(myFile.toPath(), StandardOpenOption.READ, StandardOpenOption.APPEND) 没有意义,因为APPEND 只对WRITE 有意义。由于tryLock,试图获得一个独占锁,需要WRITE访问,你必须使用FileChannel.open(myFile.toPath(), StandardOpenOption.WRITE, StandardOpenOption.APPEND)。这适用于我在 Windows 下。
  • 打开文件有多种方式(至少在 Windows 下),有无排除。正常情况下,Java 会在没有排除的情况下打开,因此不同的 Java 进程仍然可以通过FileLock 打开文件并管理排除。有非标准的OpenOptioncom.sun.nio.file.ExtendedOpenOption .NOSHARE_WRITE,你可以指定FileChannel.open(…)。当您这样做时,尝试打开文件以在另一个进程中写入将失败。看来,其他非Java程序使用了类似的模式,完全阻止了文件被另一个进程打开。
  • 不不,正常的方法是不指定该选项。当您想阻止其他非协作 Windows 程序打开文件时,您可以使用此选项。我将添加一个答案来更详细地解释这一点。

标签: java java-11


【解决方案1】:

使用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

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

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

【讨论】:

  • 这是一个非常详细的答案,完美地回答了我的问题。谢谢
猜你喜欢
  • 2015-05-09
  • 2016-06-07
  • 2023-03-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多