我注意到随机访问文件的java.io
和java.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();
}
}
这有什么理由吗?这两个甚至被视为替代品还是意味着无限期共存?
答案 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()。