在已经锁定的文件上打开文件输出流将覆盖该文件



我遇到这种情况,不明白为什么会发生。有人能帮我理解nio文件锁的行为吗?

我使用FileOutputStream打开了一个文件,在使用nio FileLock获得排他锁后,我向文件中写入了一些数据。没有释放锁。在同一文件上打开另一个FileOutputStream,意图获取锁并执行写操作,并期望此操作失败。但是打开第二个fileoutputstream会覆盖已经锁定的文件,该文件甚至在我尝试获得第二个锁之前就已经写入了数据。这是意料之中的吗?我的理解是获取排他锁可以防止对被锁文件进行任何更改。当我试图获得另一个锁时,如何防止覆盖我的文件?(如果另一个进程试图在不同的vm上获得相同文件的锁?)

我试过的示例程序:

        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覆盖了。下面的示例:

Program1:

    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();

Program2:

   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();

谢谢

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

当尝试从不同的JVM访问同一个文件时,您会偶然发现锁定不一定会阻止其他进程向锁定区域写入,它只是阻止它们锁定该区域。而且由于您正在使用截断现有文件的构造函数,因此可能会在尝试获取锁之前发生这种情况。

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

但是,也许您不想在文件中附加。我猜你想在成功获得锁的情况下重写。在这种情况下,FileOutputStream的构造函数不能帮助您,因为它们迫使您决定是截断还是追加。

解决方案是放弃旧的API并直接打开FileChannel(至少需要Java 7)。然后您有很多标准的打开选项,其中截断和追加是不同的。省略这两个允许覆盖而不急于截断文件:

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,这意味着如果文件不存在就创建文件,而CREATE_NEW则要求文件不存在。

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

文件锁只指定与其他文件锁工作。

来自Javadoc:

一个锁是否能阻止另一个程序访问被锁区域的内容是依赖于系统的,因此是不确定的。某些系统的本机文件锁定功能仅仅是建议的,这意味着程序必须协作地遵守已知的锁定协议,以保证数据的完整性。在其他系统上,本机文件锁是强制性的,这意味着如果一个程序锁定了文件的某个区域,那么实际上会阻止其他程序以违反该锁的方式访问该区域。在其他系统上,本机文件锁是建议的还是强制的,可以根据每个文件进行配置。为了确保跨平台行为的一致性和正确性,强烈建议将此API提供的锁当作咨询锁来使用。

相关内容

  • 没有找到相关文章

最新更新