假设有2个进程P1和P2,并且它们访问共享文件Foo.txt
。
假设P2正在从Foo.txt
读取。当P2正在阅读时,我不希望P1写入Foo.txt
。
所以我想我可以让P1写入Foo.tmp
,最后一步,将Foo.tmp
重命名为Foo.txt
。我的编程语言是Java
所以我的问题是,这会确保P2从Foo.txt
读取正确的数据吗? P2完成读取文件后,是否会提交重命名操作?
修改
我尝试按如下方式重新创建此方案:
我的P1代码是这样的:
File tempFile = new File(path1);
File realFile = new File(path2);
BufferedWriter writer = new BufferedWriter(new FileWriter(tempFile));
for(int i=0;i<10000;i++)
writer.write("Hello World\n");
writer.flush();
writer.close();
tempFile.renameTo(realFile);
我的P2代码是:
BufferedReader br = new BufferedReader(new FileReader(file));
String line = null;
while(true) {
while((line=br.readLine())!=null){
System.out.println(line);
Thread.sleep(1000);
}
br.close();
}
我的示例共享文件:
Test Input
Test Input
Test Input
我几乎同时开始P1和P2(P2首先开始)。
所以根据我的理解,尽管P1已经写了一个新的Foo.txt,因为P2已经在读它,它应该读取旧的Foo.txt内容,直到它重新打开BufferedReader到Foo.txt。
但实际发生的事情是P2读取Test Input
三次,如输入所预期的那样,但之后它会读取由P1写入的新内容。
P2的输出:
Test Input
Test Input
Test Input
Hello World
Hello World
Hello World
.
.
.
所以它不能正常工作。我测试这种情况错了吗?我觉得我错过了一些东西。
答案 0 :(得分:31)
UNIX rename
操作是原子操作(请参阅rename(2))。如果源路径和目标路径位于同一物理设备上,则UNIX mv
命令将使用重命名。如果目标路径位于其他设备上,则重命名将失败,mv
将复制该文件(非原子路径)。
如果目标文件路径存在,rename
将自动将其从文件系统中删除,并将其替换为新文件。在引用计数降至零之前,文件实际上不会被删除,因此如果另一个进程当前正在读取该文件,它将继续读取旧文件。一旦所有进程都关闭了旧文件,其引用计数将降至零,并且将回收文件存储空间。
答案 1 :(得分:5)
为什么不使用FileChannel.lock
?
这是一个例子:
http://examples.javacodegeeks.com/core-java/nio/filelock/create-shared-file-lock-on-file/
答案 2 :(得分:3)
Foo.txt
退出移动Foo.tmp
到Foo.txt
,则很可能会失败。 (但是如果你先删除Foo.txt
然后移动,它应该可以工作)。发生的情况是,在关闭所有文件处理程序(没有使用该文件的进程)之前,文件不会被物理删除。此外,在保留Foo.tmp
到Foo.txt
之后,您将拥有2个Foo.txt文件。一个被删除但仍在内存中打开的(基本上该文件在磁盘上没有引用)和一个实际驻留在磁盘上的文件。如果我们与#1在同一页上,请告诉我。