【问题标题】:How to lock a file如何锁定文件
【发布时间】:2012-07-17 15:24:00
【问题描述】:

我有一个write 方法,应该可以安全地将数据写入文件。

// The current file I am writing to.
FileOutputStream file = null;
...
// Synchronized version.
private void write(byte[] bytes) {
  if (file != null && file.getChannel() != null) {
    try {
      boolean written = false;
      do {
        try {
          // Lock it!
          FileLock lock = file.getChannel().lock();
          try {
            // Write the bytes.
            file.write(bytes);
            written = true;
          } finally {
            // Release the lock.
            lock.release();
          }

        } catch (OverlappingFileLockException ofle) {
          try {
            // Wait a bit
            Thread.sleep(0);
          } catch (InterruptedException ex) {
            throw new InterruptedIOException("Interrupted waiting for a file lock.");
          }
        }
      } while (!written);
    } catch (IOException ex) {
      log.warn("Failed to lock " + fileName, ex);
    }
  } else {
    log.warn("Failing - " + (file == null ? "file" : "channel") + " is null!!");
  }
}

虽然我知道它有一些皱纹,但它对我来说已经有一段时间了。

我最近更改了一个项目,该项目使用此代码在 Java 5(从 Java 6)下构建和运行,现在它看起来像死锁等待文件锁定。它是一个多线程应用程序,很可能多个线程尝试写入同一个文件。

调试器告诉我挂起的线程正在等待FileLock lock = file.getChannel().lock() 调用返回。

一些研究提出了这个有趣的小nugget,其中提到:

代表整个 Java 虚拟机持有文件锁。 它们不适合控制同一虚拟机内的多个线程对文件的访问。

那我做错了吗?如果是这样,正确的方法是什么?如果我做得对,我怎么会陷入僵局?

补充:忘了提 - 每个线程都有自己的这个对象的副本,所以代码中不应该有任何同步问题。我觉得依靠FileChannel.lock() 方法来确保写入不会交错是安全的。

也添加了:我确实使用各种synchronized 机制解决了这个问题。但是,我确实有一些悬而未决的问题:

  1. 为什么FileLock lock = file.getChannel().lock();不适合...
  2. 为什么在 Java-6 一切正常的情况下切换回 Java-5 时才出现我的问题?

【问题讨论】:

  • 如果你是唯一的作者,为什么要锁定文件?你想解决什么问题?我不会使用锁,因为它们会导致比解决的问题更多的问题。
  • 我正在锁定文件以避免交叉写入。文件锁会导致什么问题?

标签: java multithreading file file-io locking


【解决方案1】:

FileLock 仅用于进程间锁定,javadoc 读作:

"代表整个 Java 虚拟机持有文件锁。 它们不适合控制多人对文件的访问 同一虚拟机中的线程。”

要在 java 线程(相同的 JVM)之间锁定,您需要使用一些共享锁。我建议在文件写入类中使用同步块(accordingthese articles 可能表现最好):

final Object lock = new Object();

public void write(...){
  synchronized(lock){
    // do writing
  }
}

另一种方法是使用ReentrantLock,然后使用经过验证的成语

final ReentrantLock lock = new ReentrantLock();

public void write(...){
  try {
    lock.lock()
    // do the writing
  } finally {
    // forget this and you're screwed
    lock.unlock();
  }
}

【讨论】:

  • 为什么使用 ReentrantLock 而不是简单的同步块?
  • 这就是我开始相信的:(所以我需要为我写入的每个文件锁定,即使每个文件都有一个可以锁定的通道!你知道它是关于频道锁定使其不适合?
  • @jtahlborn - 同步无济于事。我使用锁解决的问题是交错写入,而不是并发访问。
  • @jtahlborn 同步块也可以工作,ReentrantLock 有更多功能。推荐阅读类级javadoc:docs.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/…
  • @OldCurmudgeon 对不起,这超出了我的能力,我能做的最好的就是参考 javadoc。我欢迎更有见识的读者加入...
【解决方案2】:

您可能需要使用文件而不是哈希图在实际代码上实现临界区概念。您可以创建一个同步块或将文件访问代码分离到一个单独的过程中并使该方法同步。

本质上,一次只有一个线程执行一个同步块。它为您提供所需的独占访问权限。

另一种方法是使用串行线程执行器,具体取决于您的功能要求。

你可能想看看这个线程: Howto synchronize file access in a shared folder using Java (OR: ReadWriteLock on network level)

【讨论】:

  • 我很抱歉 - 我已经添加到问题中 - 这种方法不应该有任何线程间争用。锁用于避免交叉写入。
  • 好先生不用道歉,但是现在问题解决了吗?基本上,您不应该在关键部分之外公开文件句柄。定义一个关键块(写入方法)而不是关键资源(文件)。每个进程调用同步方法,获取连接,附加到文件,关闭连接,为下一个人释放块。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-08-17
  • 2021-03-29
  • 1970-01-01
  • 2013-10-05
  • 2018-11-13
相关资源
最近更新 更多