FileLock如何工作?

我一直在尝试使用FileLock获取对文件的独占访问权限,以便:

  • 删除它
  • 重命名
  • 写信给它

因为在Windows上(至少),您似乎无法删除,重命名或写入已在使用的文件。 我写的代码看起来像这样:

 import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; public abstract class LockedFileOperation { public void execute(File file) throws IOException { if (!file.exists()) { throw new FileNotFoundException(file.getAbsolutePath()); } FileChannel channel = new RandomAccessFile(file, "rw").getChannel(); try { // Get an exclusive lock on the whole file FileLock lock = channel.lock(); try { doWithLockedFile(file); } finally { lock.release(); } } finally { channel.close(); } } public abstract void doWithLockedFile(File file) throws IOException; } 

以下是一些certificate问题的unit testing。 您需要在类路径上安装Apache commons-io才能运行第3次测试。

 import java.io.File; import java.io.IOException; import junit.framework.TestCase; public class LockedFileOperationTest extends TestCase { private File testFile; @Override protected void setUp() throws Exception { String tmpDir = System.getProperty("java.io.tmpdir"); testFile = new File(tmpDir, "test.tmp"); if (!testFile.exists() && !testFile.createNewFile()) { throw new IOException("Failed to create test file: " + testFile); } } public void testRename() throws IOException { new LockedFileOperation() { @Override public void doWithLockedFile(File file) throws IOException { if (!file.renameTo(new File("C:/Temp/foo"))) { fail(); } } }.execute(testFile); } public void testDelete() throws IOException { new LockedFileOperation() { @Override public void doWithLockedFile(File file) throws IOException { if (!file.delete()) { fail(); } } }.execute(testFile); } public void testWrite() throws IOException { new LockedFileOperation() { @Override public void doWithLockedFile(File file) throws IOException { org.apache.commons.io.FileUtils.writeStringToFile(file, "file content"); } }.execute(testFile); } } 

没有一个测试通过。 前2个失败,最后一个抛出此exception:

 java.io.IOException: The process cannot access the file because another process has locked a portion of the file at java.io.FileOutputStream.writeBytes(Native Method) at java.io.FileOutputStream.write(FileOutputStream.java:247) at org.apache.commons.io.IOUtils.write(IOUtils.java:784) at org.apache.commons.io.IOUtils.write(IOUtils.java:808) at org.apache.commons.io.FileUtils.writeStringToFile(FileUtils.java:1251) at org.apache.commons.io.FileUtils.writeStringToFile(FileUtils.java:1265) 

看起来lock()方法会锁定文件,然后阻止我重命名/删除/写入它。 我的假设是锁定文件会让我对该文件进行独占访问,因此我可以重命名/删除/写入它,而不必担心其他任何进程是否也在访问它。

要么我误解了FileLock要么它不适合解决我的问题。

有关另一个进程的消息仅表示系统上的某个进程已打开该文件。 它实际上并不检查该进程是否与尝试删除/重命名文件的进程相同。 在这种情况下,同一程序打开了文件。 你打开它来获得锁定。 这里的锁几乎没有价值,特别是如果你这样做是为了删除或重命名操作。

要执行您想要的操作,您需要锁定目录条目。 这在Java中不可用,可能在Windows中不可用。 这些(删除和插入)操作是primefaces操作。 这意味着操作系统负责为您锁定目录和其他文件系统结构。 如果另一个进程(或您自己的)打开了该文件,则这些操作将失败。 如果您尝试以独占方式锁定文件(目录条目)而另一个进程(或您自己的)将文件打开,则锁定将失败。 没有区别,但尝试执行锁定只会使操作变得复杂,并且在这种情况下,使操作无法进行(即,在尝试执行操作之前始终打开文件)。

现在写入文件是一个有效的锁定操作。 锁定要写入的文件或文件的一部分,然后它将起作用。 在Windows上,此锁定机制是必需的,因此另一个打开/文件描述符将无法写入锁定下的任何部分。

编辑

根据FileChannel.lock上的JavaDoc,它与调用FileChannel.lock(0L, Long.MAXVALUE, false) 。 这是从第一个字节到最后一个字节的区域的独占锁。

其次,根据FileLock上的JavaDoc

锁实际上是否阻止另一个程序访问锁定区域的内容是系统相关的,因此未指定。 某些系统的本机文件锁定function仅仅是建议性的,这意味着程序必须协作地观察已知的锁定协议以保证数据的完整性。 在其他系统上,本机文件锁是必需的,这意味着如果一个程序锁定文件的某个区域,则实际上阻止其他程序以违反锁的方式访问该区域。 在其他系统上,可以基于每个文件配置本机文件锁是建议性的还是必需的。 为了确保跨平台的一致和正确的行为, 强烈建议使用此API提供的锁,就像它们是建议锁一样

编辑

对于testWrite方法。 公共I / O静态方法上的JavaDoc是稀疏的,但是说“将一个字符串写入创建该文件的文件(如果它不存在……”)并且由于此方法采用File而不是打开的流,它可能会打开内部文件。 可能它没有打开具有共享访问权限的文件,也没有打开附加访问权限。 这意味着现有的open和lock(你打开以获取锁定的通道)阻止了这种使用。 要了解更多信息,您需要获取该方法的源代码并查看它正在执行的操作。

编辑

对不起,我纠正了。 我检查了Windows API,Windows上必须使用文件锁定。 这就是写入失败的原因。 第一次打开(您的new RandomAccessFile )和锁定文件被锁定。 打开以写入字符串成功但写入失败,因为另一个打开(文件描述符)具有强制独占锁定下的文件的完整范围 – 也就是说,在释放锁定之前,没有其他文件描述符可以写入文件。

请注意,锁定与文件描述符NOT进程或线程相关联。

您所使用的锁定是锁定文件中的区域 ,而不是文件本身,因此当区域被锁定时,您无法删除或重命名该文件。

您可能想查看Commons Transaction项目。

deleterename操作由操作系统执行,并且是primefaces操作(在大多数操作系统上),因此不需要锁定。

要将字符串写入文件,首先写入临时文件(例如foo.tmp)然后在准备好后重命名它会更简单。

指定Java文件锁只是为了防止其他锁,而不是其他锁。 它们在特定平台上的行为,即任何额外的语义,都是特定于平台的。

您应该在执行任何操作(如重命名或删除)之前使用方法release()释放该文件。