在已锁定的文件上打开文件输出流会覆盖它

Opening a file output stream on a file that is already locked overwrites it

我遇到过这种情况,但不明白为什么会这样。谁能帮我理解 nio 文件锁的行为。

我使用 FileOutputStream 打开了一个文件,在使用 nio FileLock 获取排他锁后,我将一些数据写入文件。没有释放锁。在同一个文件上打开另一个 FileOutputStream 以获取锁并执行写操作并期望这是 fail.But 打开第二个 fileoutputstream 覆盖了已经锁定的文件,该文件甚至在我尝试获得第二个之前就已经写入了数据锁。这是预期的吗?我的理解是获得独占锁会阻止对锁定文件的任何更改。尝试获取另一把锁时如何防止覆盖我的文件? (好像另一个进程试图在不同的虚拟机上锁定同一个文件?)

我试过的示例程序:

        File fileToWrite = new File("C:\temp\myfile.txt");
        FileOutputStream fos1 = new FileOutputStream(fileToWrite);
        FileOutputStream fos2 =null;
        FileLock lock1,lock2 =null;
        lock1=fos1.getChannel().tryLock();
        if(lock1!=null){
            //wrote date to myfile.txt after acquiring lock
            fos1.write(data.getBytes());
            //opened myfile.txt again and this replaced the file
            fos2 = new FileOutputStream(fileToWrite);
            //got an overlappingfilelock exception here
            lock2=fos2.getChannel().tryLock();
            fos2.write(newdata.getBytes());
            }

        lock1.release();
        fos1.close();
        if(lock2!=null)
            lock2.release();
        fos2.close();

还尝试将以上内容拆分为两个程序。第一个执行并在第一个等待时开始第二个。被程序 1 锁定的文件被程序 2 覆盖。示例如下:

程序 1:

    File fileToWrite = new File("C:\temp\myfile.txt");
    FileOutputStream fos1 = new FileOutputStream(fileToWrite);
    FileLock lock1 =null;
    lock1=fos1.getChannel().tryLock();
    if(lock1!=null){
        //wrote date to myfile.txt after acquiring lock
        fos1.write(data.getBytes());
        System.out.println("wrote data and waiting");
        //start other program while sleep
        Thread.sleep(10000);
        System.out.println("finished wait");
        }

    lock1.release();
    fos1.close();

程序 2:

   File fileToWrite = new File("C:\temp\myfile.txt");
    System.out.println("opening 2nd out stream");
    //this overwrote the file
    FileOutputStream fos2 = new FileOutputStream(fileToWrite);
    FileLock lock2 =null;
    lock2=fos2.getChannel().tryLock();
    //lock is null here
    System.out.println("lock2="+lock2);
    if(lock2!=null){
        //wrote date to myfile.txt after acquiring lock
        System.out.println("writing  NEW data");
        fos2.write(newdata.getBytes());
        }

    if(lock2!=null)
        lock2.release();
    fos2.close();

谢谢

仅文件锁仅指定用于对抗其他文件锁。

来自Javadoc

Whether or not a lock actually prevents another program from accessing the content of the locked region is system-dependent and therefore unspecified. The native file-locking facilities of some systems are merely advisory, meaning that programs must cooperatively observe a known locking protocol in order to guarantee data integrity. On other systems native file locks are mandatory, meaning that if one program locks a region of a file then other programs are actually prevented from accessing that region in a way that would violate the lock. On yet other systems, whether native file locks are advisory or mandatory is configurable on a per-file basis. To ensure consistent and correct behavior across platforms, it is strongly recommended that the locks provided by this API be used as if they were advisory locks.

当您获取 FileLock 时,您就为整个 JVM 获取了它。这就是为什么在同一个 JVM 中创建更多 FileOutputStream 和覆盖同一个文件永远不会被 FileLock 阻止——JVM 拥有锁。因此,OverlappingFileLockException 并不是要告诉您锁不可用(tryLock 会通过返回 null 发出信号),而是要告诉您有一个编程错误:试图获取您已经拥有的锁。

当试图从不同的 JVM 访问同一个文件时,您会偶然发现这样一个事实,即锁定并不一定会阻止其他进程写入锁定区域,它只是阻止它们锁定该区域。并且由于您正在使用 constructor which truncates existing files,这可能会在您尝试获取锁之前发生。

一种解决方案是使用 new FileOutputStream(fileToWrite, true) 来避免截断文件。无论您是在同一个 JVM 中还是在不同的进程中打开文件,这都有效。

但是,您可能不想 追加 到文件。我猜您想在成功获取锁的情况下进行覆盖。在这种情况下,FileOutputStream 的构造函数不会帮助您,因为它们迫使您决定是截断还是追加。

解决方案是放弃旧的 API 和 open the FileChannel directly (requires at least Java 7). Then you have plenty of standard open options where truncating and appending 是不同的。省略两者允许覆盖而不急切地截断文件:

try(FileChannel fch=FileChannel.open(fileToWrite.toPath(),
                                     StandardOpenOption.CREATE, StandardOpenOption.WRITE)){
  try(FileLock lock=fch.tryLock()) {
    if(lock!=null) {
      // you can directly write into the channel
      // but in case you really need an OutputStream:
      OutputStream fos=Channels.newOutputStream(fch);
      fos.write(testData.getBytes());
      // you may explicitly truncate the file to the actually written content:
      fch.truncate(fch.position());
      System.out.println("waiting while holding lock...");
      LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(5));
    }
    else System.out.println("couldn't acquire lock");
  }
}

因为它需要 Java 7,所以您可以使用自动资源管理进行清理。请注意,此代码使用 CREATE which implies the already familiar behavior of creating the file if it doesn’t exists, in contrast to CREATE_NEW,这将 要求 该文件不存在。

由于指定的选项,open 操作可能会创建文件但不会截断它。后续所有操作只有在获取锁成功后才会执行。