文件锁定如何工作?

2022-09-04 20:16:21

我一直在尝试使用来获得对文件的独占访问权限,以便: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才能运行第三个测试。

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


答案 1

有关另一个进程的消息仅表示系统上的某些进程已打开该文件。它实际上不会检查该过程是否恰好与尝试删除/重命名文件的过程相同。在这种情况下,同一程序打开了该文件。您已经打开它以获取锁定。此处的锁几乎没有价值,尤其是在执行删除或重命名操作时。

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

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

编辑

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

其次,根据JavaDoc关于FileLock

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

编辑

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

编辑

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

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


答案 2

您使用的锁定是锁定文件内的区域,但不是文件本身,因此在区域锁定时,您无法删除或重命名文件。

你可能想看看共享资源交易项目。