【问题标题】:Java FileLock for Reading and Writing用于读写的 Java FileLock
【发布时间】:2010-02-15 21:20:22
【问题描述】:

我有一个进程会被 cron 频繁调用,以读取其中包含某些与移动相关的命令的文件。我的进程需要读取和写入此数据文件 - 并保持锁定以防止其他进程在此期间接触它。用户可以执行一个完全独立的过程来(潜在的)写入/附加到这个相同的数据文件。我希望这两个进程能够正常运行,并且一次只能访问一个文件。

nio FileLock 似乎是我需要的(没有编写我自己的信号量类型文件),但我无法锁定它以供阅读。我可以锁定和写得很好,但是在阅读时尝试创建锁定时,我得到了一个 NonWritableChannelException。甚至可以锁定文件以供阅读吗?似乎 RandomAccessFile 更接近我的需要,但我不知道如何实现它。

下面是失败的代码:

FileInputStream fin = new FileInputStream(f);
FileLock fl = fin.getChannel().tryLock();
if(fl != null) 
{
  System.out.println("Locked File");
  BufferedReader in = new BufferedReader(new InputStreamReader(fin));
  System.out.println(in.readLine());
          ...

FileLock 行抛出异常。

java.nio.channels.NonWritableChannelException
 at sun.nio.ch.FileChannelImpl.tryLock(Unknown Source)
 at java.nio.channels.FileChannel.tryLock(Unknown Source)
 at Mover.run(Mover.java:74)
 at java.lang.Thread.run(Unknown Source)

查看 JavaDocs,它说

尝试写入最初未打开用于写入的通道时引发未经检查的异常。

但我不一定需要给它写信。当我尝试为写入目的创建 FileOutpuStream 等时,我很高兴直到我尝试在同一个文件上打开 FileInputStream。

【问题讨论】:

  • 你试过使用三个参数方法调用,FileLock lock(long position, long size, boolean shared)吗?我以前从未使用过 FileLock,所以我不会发布作为答案,但我认为使用该方法调用可能会有所帮助,因为听起来您需要共享锁而不是排他锁,因为您想在文件有时写入文件锁上它。
  • 我认为这样做的目的是只锁定文件的一部分,但我想锁定整个文件以防止和可能的损坏。

标签: java file-locking filelock


【解决方案1】:
  1. 您是否知道锁定文件不会阻止其他进程访问它,除非它们也使用锁?
  2. 您必须通过可写通道锁定。在“rw”模式下通过RandomAccessFile 获取锁,然后打开您的FileInputStream。确保将两者都关闭!

【讨论】:

  • a) 是的,我将编写这两个进程并计划在两者中实现类似的锁定过程 b) 我没有意识到您可以直接获得 RandomAccessFile 上的锁定。要使用 File[Input|Output]Stream,我需要创建一个新的 FileInputStream(raf.getFD())。但是,无论哪种方式直接使用 RandomAccessFile 对象的输入流,我仍然可以从文件中读取。谢谢
  • 嗯? (a) 不能直接从 RandomAccessFile 获取锁,必须先获取它的 FileChannel; (b) RandomAccessFile 没有输入流,但它实现了 DataInput,因此您可以直接从中读取。
  • 我注意到在 linux(Oracle Linux 服务器,类似于 Red Hat Linux)中,java 中的文件锁定在操作系统实例之间起作用。也就是说,它将锁定在一个操作系统中,但不会锁定在另一个盒子中。
  • @DonSmith 在 Javadoc 中有说明。网络文件锁定总是有问题,需要避免,网络文件确实如此。
【解决方案2】:

如果你使用tryLock(0L, Long.MAX_VALUE, true)创建锁会更好。

这会创建一个共享锁,这对于读取来说是正确的。

tryLock()tryLock(0L, Long.MAX_VALUE, false) 的简写,即它请求独占写锁。

【讨论】:

  • 反响很好。该程序已经上线,但肯定可以在下一阶段进行更新。现在我们看到了它的大量使用,以至于独占锁在某些情况下变得很麻烦。我一定会在下次更新时记住这一点。
  • 你为什么说共享锁是阅读的正确方法,这当然取决于你的用例?如果我想确保只有多个进程中的 1 个进程读取文件(例如,代理监视上传目录以处理新文件),那么我认为不需要共享锁。
  • 我这样写是因为阅读不是相互排斥的。实际上,多个线程可以在不干扰的情况下从一个文件中读取,而写入始终是独占的。请记住,这是对原始问题的回答,并不意味着适用于每种情况的绝对真理。不过,在 99% 的用例中都是如此。
【解决方案3】:

我写了一个测试程序和bash命令来确认文件锁的有效性:

import java.io.File;
import java.io.RandomAccessFile;
import java.nio.channels.FileLock;

public class FileWriterTest
{
    public static void main(String[] args) throws Exception
    {
        if (args.length != 4)
        {
            System.out.println("Usage: FileWriterTest <filename> <string> <sleep ms> <enable lock>");
            System.exit(1);
        }

        String filename = args[0];
        String data = args[1];
        int sleep = Integer.parseInt(args[2]);
        boolean enableLock = Boolean.parseBoolean(args[3]);

        try (RandomAccessFile raFile = new RandomAccessFile(new File(filename), "rw"))
        {
            FileLock lock = null;
            if (enableLock)
            {
                lock = raFile.getChannel().lock();
            }

            Thread.sleep(sleep);
            raFile.seek(raFile.length());
            System.out.println("writing " + data + " in a new line; current pointer = " + raFile.getFilePointer());
            raFile.write((data+"\n").getBytes());

            if (lock != null)
            {
                lock.release();
            }
        }
    }
}

使用这个 bash 命令运行以检查它是否有效:

for i in {1..1000}
do
java FileWriterTest test.txt $i 10 true &
done

您应该看到每 10 毫秒(从输出)写入一次,最后所有数字都会出现在文件中。

输出:

/tmp wc -l test.txt
1000 test.txt
/tmp

没有锁的相同测试显示数据丢失:

for i in {1..1000}
do
java FileWriterTest test.txt $i 10 false &
done

输出:

/tmp wc -l test.txt
764 test.txt
/tmp

修改它来测试 tryLock 应该很容易。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-05-09
    • 1970-01-01
    • 2019-10-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多