随机访问文件FileLock:java.io与java.nio

时间:2016-09-02 13:05:52

标签: java nio java-io random-access filelock

我注意到随机访问文件的java.iojava.nio实现与FileLocks的处理方式略有不同。

好像(在Windows上)java.io为您提供强制文件锁定,java.nio分别在请求时为您提供建议文件锁定。强制文件锁定意味着锁定适用于所有进程,并且建议适用于遵循相同锁定协议的良好行为进程。

如果我运行以下示例,我可以手动删除*.nio文件,而*.io文件拒绝删除。

import java.io.*;
import java.lang.management.ManagementFactory;
import java.nio.*;
import java.nio.channels.*;
import java.nio.file.*;

public class NioIoLock {

    public static void main(String[] args) throws IOException, InterruptedException {
        String workDir = System.getProperty("user.dir");

        FileChannel channelIo, channelNio;
        FileLock lockIo, lockNio;

        // use io
        {
            String fileName = workDir
                    + File.separator
                    + ManagementFactory.getRuntimeMXBean().getName()
                    + ".io";
            File lockFile = new File(fileName);
            lockFile.deleteOnExit();
            RandomAccessFile file = new RandomAccessFile(lockFile, "rw");            

            channelIo = file.getChannel();
            lockIo = channelIo.tryLock();
            if (lockIo != null) {                   
                channelIo.write(ByteBuffer.wrap("foobar".getBytes("UTF-8")));
            }

        }

        // use nio
        {
            Path workDirPath = Paths.get(workDir);
            Path file = workDirPath.resolve(
                    Paths.get(ManagementFactory.getRuntimeMXBean().getName() + ".nio"));

            // open/create test file
            channelNio = FileChannel.open(
                    file, StandardOpenOption.READ, StandardOpenOption.WRITE,
                    StandardOpenOption.CREATE, StandardOpenOption.DELETE_ON_CLOSE);

            // lock file
            lockNio = channelNio.tryLock();
            if (lockNio != null) {
                channelNio.write(ByteBuffer.wrap("foobar".getBytes("UTF-8")));
            }

        }

        // do not release locks for some time
        Thread.sleep(10000);

        // release io lock and channel
        if (lockIo != null && lockIo.isValid()) {
            lockIo.release();
        }
        channelIo.close();

        // release nio lock and channel
        if (lockNio != null && lockNio.isValid()) {
            lockNio.release();
        }
        channelNio.close();
    }

}

这有什么理由吗?这两个甚至被视为替代品还是意味着无限期共存?

1 个答案:

答案 0 :(得分:8)

TL; DR:它不是关于锁,而是关于文件的打开方式。你在io中看到的是Windows在Windows 2000之前可以做的最好的事情,即使文件只是为了阅读而打开它也是如此 - 它无法删除该文件。您在nio中看到的是使用自Windows 2000以来引入的新功能的改进,但如果您选择,您仍可以在nio中使用旧行为。决定不将该功能移植到io所做的内容中。

全文:我删除了与锁定相关的所有代码(见下文)以及写入文件,它的工作方式与您的代码完全相同;但是,我还发现如果你在打开" nio"时指定ExtendedOpenOption.NOSHARE_DELETE。通道,然后当您尝试删除任何两个文件时的行为是相同的(在代码中取消注释并尝试)。还找到this question并且它有一些解释,不确定它是否会有所帮助。

import java.io.*;
import java.lang.management.ManagementFactory;
import java.nio.*;
import java.nio.channels.*;
import java.nio.file.*;

public class NioIoLock {

    public static void main(String[] args)
            throws IOException, InterruptedException {
        String workDir = System.getProperty("user.dir");

        FileChannel channelIo, channelNio;
        FileLock lockIo, lockNio;

        // use io
        {
            String fileName = workDir + File.separator
                + ManagementFactory.getRuntimeMXBean().getName() + ".io";
            File lockFile = new File(fileName);
            lockFile.deleteOnExit();
            RandomAccessFile file = new RandomAccessFile(lockFile, "rw");
            channelIo = file.getChannel();
        }

        // use nio
        {
            Path workDirPath = Paths.get(workDir);
            Path file = workDirPath.resolve(Paths
                .get(ManagementFactory.getRuntimeMXBean().getName() + ".nio"));

            // open/create test file
            channelNio = FileChannel.open(file, StandardOpenOption.READ,
                StandardOpenOption.WRITE, StandardOpenOption.CREATE,
                StandardOpenOption.DELETE_ON_CLOSE
                /*, com.sun.nio.file.ExtendedOpenOption.NOSHARE_DELETE*/);
        }

        // do not release locks for some time
        Thread.sleep(10000);
    }
}

更新1:实际上,我仍然不知道这背后的基本原理,但机制很清楚:RandomAccessFile构造函数最终从{{3}调用以下本机代码来自io_util_md.c:

FD
winFileHandleOpen(JNIEnv *env, jstring path, int flags)
{
    ...
    const DWORD sharing =
        FILE_SHARE_READ | FILE_SHARE_WRITE;
    ... // "sharing" not updated anymore
    h = CreateFileW(
        pathbuf,            /* Wide char path name */
        access,             /* Read and/or write permission */
        sharing,            /* File sharing flags */
        NULL,               /* Security attributes */
        disposition,        /* creation disposition */
        flagsAndAttributes, /* flags and attributes */
        NULL);
    ...
}

因此,FILE_SHARE_DELETE标志未设置,您无法设置它。另一方面,调用java.nio.channels.FileChannel.open(Path, OpenOption...) method winFileHandleOpen会考虑此标记:

    private static FileDescriptor open(String pathForWindows,
                                       String pathToCheck,
                                       Flags flags,
                                       long pSecurityDescriptor)
        throws WindowsException
    {
        ...
        int dwShareMode = 0;
        if (flags.shareRead)
            dwShareMode |= FILE_SHARE_READ;
        if (flags.shareWrite)
            dwShareMode |= FILE_SHARE_WRITE;
        if (flags.shareDelete)
            dwShareMode |= FILE_SHARE_DELETE;
        ...
        // open file
        long handle = CreateFile(pathForWindows,
                                 dwDesiredAccess,
                                 dwShareMode,
                                 pSecurityDescriptor,
                                 dwCreationDisposition,
                                 dwFlagsAndAttributes);
        ...
    }

更新2:来自eventually

  

改变共享模式的建议是RFE 6357433.这样做会很棒但它可能会破坏现有的应用程序(因为自jdk1.0以来当前的行为已经存在)。向RandomAccessFile添加特殊模式以解决Windows问题可能也不是正确的方法。此外,有一些建议,这可以帮助解决删除具有文件映射的文件的问题。事实并非如此,因为文件映射仍会阻止文件被删除。无论如何,对于jdk7,我们正在开发一个新的文件系统API,它将满足这里的一些要求。 Windows实现做了正确的事情,以便可以删除打开的文件。

另请参阅此处引用的JDK-6607535

  

客户在java.net论坛上指出,使用FileInputStream在Windows上打开文件会导致其他进程无法删除该文件。这是在编写代码时的预期行为,因为在打开文件期间指定的共享标志是FILE_SHARE_READ和FILE_SHARE_WRITE。请参阅io_util_md.c,fileOpen。但是,Windows 2000提供了一个新标志FILE_SHARE_DELETE,它允许另一个进程在文件仍处于打开状态时删除它。

最后

  

周围的工作

     

对于jdk7,解决方法是使用新的文件系统API。因此,不使用新的FileInputStream(f)和新的FileOutputStream(f),而是使用f.toPath()。newInputStream()和f.toPath()。newOutputStream()。