I've noticed that java.io
and java.nio
implementations of random access files differ slightly with respect to how FileLocks
are handled.
It appears as though (on Windows) java.io
gives you a mandatory file lock and java.nio
gives you an advisory file lock upon requesting it respectively. A mandatory file lock means that the lock holds for all processes and an advisory holds for well behaving processes that follow the same locking protocol.
If I run the following example, I'm able to delete the *.nio
file manually, while *.io
file refuses to be deleted.
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();
}
}
Is there a reason for this? Are these two even considered as alternatives or are they meant to coexist indefinitely?
Java RandomAccessFile provides the facility to read and write data to a file. RandomAccessFile works with file as large array of bytes stored in the file system and a cursor using which we can move the file pointer position.
The contents of the files cannot be read or write. The RandomAccessFile class has methods that perform the direct access to data of any part of the file. RandomAccessFile class also provides the facilities to read and write data from a single file without closing the streams for each read or write.
A random access file behaves like a large array of bytes stored in the file system. There is a kind of cursor, or index into the implied array, called the file pointer; input operations read bytes starting at the file pointer and advance the file pointer past the bytes read.
Java NIO works as the second I/O system after standard Java IO with some added advanced features. It provides a different way of working with I/O than the standard IO. Like Java.io package which contains all the classes required for Java input and output operations, the java.
TL;DR: It's not about locks, it's about the way files are opened. What you see in io
is the best Windows could do prior to Windows 2000, and it did that even when files were opened for reading only -- it wasn't possible to delete that file. What you see in nio
is an improvement that uses a new capability introduced since Windows 2000, but you still can have your old behavior in nio
if you choose to. It was decided not to port that capability into what io
does.
Full story: I removed all the code related to locking (see below) as well as writing to the files and it works absolutely the same as your code does; however, I also found that if you specify the ExtendedOpenOption.NOSHARE_DELETE
when opening the "nio" channel, then behavior when you try to delete any of the two files is the same (uncomment it in the code and try). Also found this question and it has some explanation, not sure if it will help.
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);
}
}
Update 1: Actually, I still don't know the rationale behind this, but the mechanics are clear: RandomAccessFile
constructor eventually calls the following native code from method winFileHandleOpen from 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);
...
}
So, FILE_SHARE_DELETE
flag is not set and there's nothing you can do to set it. On the other hand, calling java.nio.channels.FileChannel.open(Path, OpenOption...)
eventually takes this flag into account:
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);
...
}
Update 2: From JDK-6607535:
The suggestion to change the sharing mode is RFE 6357433. It would be great to do that but it may break existing applications (since the current behavior has existed since jdk1.0). Adding a special mode to RandomAccessFile to work around a Windows issues is probably not the right approach either. Also, there is some suggestion that this can help with the issue of deleting files that have a file mapping. This is not so, as a file mapping still prevents a file from being deleted. Anyway, for jdk7 we are working on a new file system API that will address some of the requirements here. The Windows implementation does the right thing so that opened files can be deleted.
Also see JDK-6357433 referenced from there:
A customer has pointed out on the java.net forums that opening a file on Windows using a FileInputStream causes other processes to be unable to delete the file. This is intended behavior as the code is currently written, as the sharing flags specified during the opening of the file are FILE_SHARE_READ and FILE_SHARE_WRITE. See io_util_md.c, fileOpen. However, Windows 2000 supplies a new flag, FILE_SHARE_DELETE, which allows another process to delete the file while it is still open.
and finally
WORK AROUND
For jdk7, the workaround is to use new file system API. So instead of new FileInputStream(f) and new FileOutputStream(f), use f.toPath().newInputStream() and f.toPath().newOutputStream().
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With