FileLock如何工作?

时间:2010-10-26 16:26:25

标签: java file-io locking

我一直在尝试使用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;
}

以下是一些演示此问题的单元测试。您需要在类路径上安装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个失败,最后一个抛出此异常:

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,要么它不适合解决我的问题。

5 个答案:

答案 0 :(得分:16)

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

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

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

修改

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

其次,根据FileLock上的JavaDoc

  

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

修改

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

修改

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

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

答案 1 :(得分:1)

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

您可能需要查看Commons Transaction项目。

答案 2 :(得分:1)

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

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

答案 3 :(得分:0)

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

答案 4 :(得分:0)

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