使用 FileLock 锁定 Java 中的文件

Lock a file in Java with FileLock

我正在尝试写入一个文件,使用 Java FileLock, to prohibit all other processes and threads from reading from it or writing to it until I'm finished with it. Given this question 及其答案,在我看来,这是我想要的完美工具 -- 用于文件访问的互斥体。

不过,我很在意这篇来自JavaDocs的文字:

File locks are held on behalf of the entire Java virtual machine. They are not suitable for controlling access to a file by multiple threads within the same virtual machine.

有人可以减轻我的恐惧或为我指明正确的方向吗?听起来 FileLock 根本无法将另一个线程保留在文件之外,即使另一个线程已经获得它也是如此。如果是这种情况,是否有另一种规范的 Java 方法可以保护其他线程?

FileLock 是进程级别的锁,因此不会保护文件免受具有锁的进程中多个线程的并发访问。

您需要结合使用 FileLock 来防止来自其他进程的并发访问和您进程中的一些其他同步机制(例如 synchronized 访问文件的方法)来保护来自您自己的线程的并发访问。

我会按如下方式实现:

interface FileOperator {
  public void operate(File file);
}

class FileProxy {
  private static final ConcurrentHashMap<URI, FileProxy> map =
    new ConcurrentHashMap<>();

  private final Semaphore mutex = new Semaphore(1, true);

  private final File file;

  private final URI key;

  private FileProxy(File file) {
    this.file = file;
    this.key = file.toURI();
  }

  public static void operate(URI uri, FileOperator operator) {
    FileProxy curProxy = map.get(uri);
    if(curProxy == null) {
      FileProxy newProxy = new FileProxy(new File(uri));
      FileProxy curProxy = map.putIfAbsent(newProxy.key, newProxy);
      if(curProxy == null) {
        curProxy = newProxy; // FileProxy was not in the map
      }
    }

    try {
      curProxy.mutex.acquire();
      operator.operate(curProxy.file);
    } finally {
      curProxy.mutex.release();
    }
  }
}

正在使用文件的线程实现 FileOperator 或类似的东西。文件隐藏在 FileProxy 后面,该 FileProxy 维护静态 ConcurrentHashMap 键(URI,或绝对路径,或其他一些文件不变)值 (FileProxy) 对。每个 FileProxy 维护一个 Semaphore 作为互斥量 - 这是用一个许可初始化的。当调用静态 operate 方法时,如果 none 存在,则从 URI 创建一个新的 FileProxyFileOperator 然后被添加到 FileProxy 队列; acquire 在互斥量上调用,确保同一时刻只有一个线程可以操作该文件;最后 FileOperator 发挥作用。

在此实现中,FileProxy 对象永远不会从 ConcurrentHashMap 中删除 - 如果这是一个问题,那么解决方案是将 FileProxy 对象包装在 WeakReferenceSoftReference 以便它们可以被垃圾收集,然后在 reference.get() == null 时调用 map.replace 以确保只有一个线程替换 GC 的引用。