Java: 如何在文件锁定时阻止 read/writing 文件
Java: How to hold off read/writing a file while it's locked
我有许多单独的应用程序(全部用 Java 编写)需要在短时间内访问文件。其中一些进程需要读取权限,而其他进程则需要更新文件。为了让所有这些应用程序都能很好地运行,我想编写一些代码,使用 OS 锁在每个应用程序需要时获得对文件的独占访问权限。
执行此操作的明显方法 RandomAccessFile myFile = new RandomAccessFile(file, "rw")
,但是如果另一个进程已经拥有锁,这将失败。我需要的是后退并重试的能力。
我希望编写一些使用 channel.tryLock()
的代码来测试是否已取出锁。问题是我需要一个通道,但我似乎无法在不取出锁的情况下获得该通道对象!
更新
我需要找到一种方法来检查文件是否被锁定。我想在不抛出异常的情况下执行此操作。
我的代码的简化版本是:
void myMethod(File myFile) {
try (
RandomAccessFile myFile = new RandomAccessFile(myFile, "rw"); // Exception here
FileChannel myChannel = myFile.getChannel();
FileLock myLock = lockFile(myChannel )
) {
// Stuff to read and/or write the file
}
}
private FileLock lockFile(FileChannel channel) throws Exception {
FileLock lock;
while (lock = channel.tryLock() == null) {
Thread.sleep(100);
}
return lock;
}
问题是,如果文件被锁定,它会在突出显示的行上失败(通过抛出异常)- 在可以获得文件锁定的点之前。
获取频道的其他变体如FileChannel channel = FileChannel.open(myFile.toPath(), StandardOpenOption.WRITE, StandardOpenOption.APPEND)
也会抛出异常:
Exception in thread "main" java.nio.file.FileSystemException: path\to\my\file.txt: The process cannot access the file because it is being used by another process.
那么我怎样才能获得一个通道来测试锁而不抛出异常呢?
使用 FileLock
的标准方法是打开文件,例如通过 FileChannel.open
,然后是 tryLock
。锁的存在不会阻止其他进程打开文件。
这可以通过以下程序证明:
import java.io.IOException;
import java.nio.channels.*;
import java.nio.file.*;
class Locking {
public static void main(String[] args) throws IOException, InterruptedException {
if(args.length > 0) {
String me = String.format("%6s ", ProcessHandle.current());
Path p = Paths.get(args[0]);
try(FileChannel fc = FileChannel.open(p,
StandardOpenOption.WRITE, StandardOpenOption.APPEND)) {
FileLock l = fc.tryLock();
if(l == null) System.out.println(me + "could not acquire lock");
else {
System.out.println(me + "got lock");
Thread.sleep(3000);
System.out.println(me + "releasing lock");
l.release();
}
}
}
else {
Path p = Files.createTempFile("lock", "test");
String[] command = {
Paths.get(System.getProperty("java.home"), "bin", "java").toString(),
"-cp", System.getProperty("java.class.path"),
"Locking", p.toString()
};
ProcessBuilder b = new ProcessBuilder(command).inheritIO();
Process p1 = b.start(), p2 = b.start(), p3 = b.start();
p1.waitFor();
p2.waitFor();
p3.waitFor();
Files.delete(p);
}
}
}
打印出类似的东西
12116 got lock
13948 could not acquire lock
13384 could not acquire lock
12116 releasing lock
可以是demonstrated online on tio.run
虽然此程序在 Windows 下工作相同,但此操作系统支持打开未共享的文件,防止其他进程打开。如果不同的进程以这种方式打开文件,我们甚至无法打开它来探测锁定状态。
这不是打开文件的方式,Java,但是,有一个非标准的打开选项可以复制该行为,com.sun.nio.file.ExtendedOpenOption.NOSHARE_WRITE
。在最近的 JDK 中,它位于 jdk.unsupported
模块中。
当我们运行下面扩展测试程序时Windows
import java.io.IOException;
import java.nio.channels.*;
import java.nio.file.*;
import java.util.HashSet;
import java.util.Set;
class LockingWindows {
public static void main(String[] args) throws IOException, InterruptedException {
if(args.length > 0) {
String me = String.format("%6s ", ProcessHandle.current());
Path p = Paths.get(args[0]);
Set<OpenOption> options
= Set.of(StandardOpenOption.WRITE, StandardOpenOption.APPEND);
if(Boolean.parseBoolean(args[1])) options = addExclusive(options);
try(FileChannel fc = FileChannel.open(p, options)) {
FileLock l = fc.tryLock();
if(l == null) System.out.println(me + "could not acquire lock");
else {
System.out.println(me + "got lock");
Thread.sleep(3000);
System.out.println(me + "releasing lock");
l.release();
}
}
}
else {
Path p = Files.createTempFile("lock", "test");
String[] command = {
Paths.get(System.getProperty("java.home"), "bin", "java").toString(),
"-cp", System.getProperty("java.class.path"),
"LockingWindows", p.toString(), "false"
};
ProcessBuilder b = new ProcessBuilder(command).inheritIO();
for(int run = 0; run < 2; run++) {
Process p1 = b.start(), p2 = b.start(), p3 = b.start();
p1.waitFor();
p2.waitFor();
p3.waitFor();
if(run == 0) {
command[command.length - 1] = "true";
b.command(command);
System.out.println("\nNow with exclusive mode");
}
}
Files.delete(p);
}
}
private static Set<OpenOption> addExclusive(Set<OpenOption> options) {
OpenOption o;
try {
o = (OpenOption) Class.forName("com.sun.nio.file.ExtendedOpenOption")
.getField("NOSHARE_WRITE").get(null);
options = new HashSet<>(options);
options.add(o);
} catch(ReflectiveOperationException | ClassCastException ex) {
System.err.println("opening exclusive not supported");
}
return options;
}
}
我们会得到类似
的东西
2356 got lock
6412 could not acquire lock
9824 could not acquire lock
2356 releasing lock
Now with exclusive mode
9160 got lock
Exception in thread "main" java.nio.file.FileSystemException: C:\Users\...\Temp\lock13936982436235244405test: The process cannot access the file because it is being used by another process
at java.base/sun.nio.fs.WindowsException.translateToIOException(WindowsException.java:92)
at java.base/sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:103)
at java.base/sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:108)
at java.base/sun.nio.fs.WindowsFileSystemProvider.newFileChannel(WindowsFileSystemProvider.java:121)
at java.base/java.nio.channels.FileChannel.open(FileChannel.java:298)
at LockingWindows.main(LockingWindows.java:148)
Exception in thread "main" java.nio.file.FileSystemException: C:\Users\...\Temp\lock13936982436235244405test: The process cannot access the file because it is being used by another process
at java.base/sun.nio.fs.WindowsException.translateToIOException(WindowsException.java:92)
at java.base/sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:103)
at java.base/sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:108)
at java.base/sun.nio.fs.WindowsFileSystemProvider.newFileChannel(WindowsFileSystemProvider.java:121)
at java.base/java.nio.channels.FileChannel.open(FileChannel.java:298)
at LockingWindows.main(LockingWindows.java:148)
9160 releasing lock
与您测试结果的相似性表明 Windows 您 运行 与 Java 程序同时使用的程序确实使用了这种模式。
对于您的 Java 程序,只要您不使用该模式,就不会出现此类问题。仅当您必须与另一个未使用协作锁定的 Windows 程序交互时,您才需要处理此问题。
我有许多单独的应用程序(全部用 Java 编写)需要在短时间内访问文件。其中一些进程需要读取权限,而其他进程则需要更新文件。为了让所有这些应用程序都能很好地运行,我想编写一些代码,使用 OS 锁在每个应用程序需要时获得对文件的独占访问权限。
执行此操作的明显方法 RandomAccessFile myFile = new RandomAccessFile(file, "rw")
,但是如果另一个进程已经拥有锁,这将失败。我需要的是后退并重试的能力。
我希望编写一些使用 channel.tryLock()
的代码来测试是否已取出锁。问题是我需要一个通道,但我似乎无法在不取出锁的情况下获得该通道对象!
更新
我需要找到一种方法来检查文件是否被锁定。我想在不抛出异常的情况下执行此操作。
我的代码的简化版本是:
void myMethod(File myFile) {
try (
RandomAccessFile myFile = new RandomAccessFile(myFile, "rw"); // Exception here
FileChannel myChannel = myFile.getChannel();
FileLock myLock = lockFile(myChannel )
) {
// Stuff to read and/or write the file
}
}
private FileLock lockFile(FileChannel channel) throws Exception {
FileLock lock;
while (lock = channel.tryLock() == null) {
Thread.sleep(100);
}
return lock;
}
问题是,如果文件被锁定,它会在突出显示的行上失败(通过抛出异常)- 在可以获得文件锁定的点之前。
获取频道的其他变体如FileChannel channel = FileChannel.open(myFile.toPath(), StandardOpenOption.WRITE, StandardOpenOption.APPEND)
也会抛出异常:
Exception in thread "main" java.nio.file.FileSystemException: path\to\my\file.txt: The process cannot access the file because it is being used by another process.
那么我怎样才能获得一个通道来测试锁而不抛出异常呢?
使用 FileLock
的标准方法是打开文件,例如通过 FileChannel.open
,然后是 tryLock
。锁的存在不会阻止其他进程打开文件。
这可以通过以下程序证明:
import java.io.IOException;
import java.nio.channels.*;
import java.nio.file.*;
class Locking {
public static void main(String[] args) throws IOException, InterruptedException {
if(args.length > 0) {
String me = String.format("%6s ", ProcessHandle.current());
Path p = Paths.get(args[0]);
try(FileChannel fc = FileChannel.open(p,
StandardOpenOption.WRITE, StandardOpenOption.APPEND)) {
FileLock l = fc.tryLock();
if(l == null) System.out.println(me + "could not acquire lock");
else {
System.out.println(me + "got lock");
Thread.sleep(3000);
System.out.println(me + "releasing lock");
l.release();
}
}
}
else {
Path p = Files.createTempFile("lock", "test");
String[] command = {
Paths.get(System.getProperty("java.home"), "bin", "java").toString(),
"-cp", System.getProperty("java.class.path"),
"Locking", p.toString()
};
ProcessBuilder b = new ProcessBuilder(command).inheritIO();
Process p1 = b.start(), p2 = b.start(), p3 = b.start();
p1.waitFor();
p2.waitFor();
p3.waitFor();
Files.delete(p);
}
}
}
打印出类似的东西
12116 got lock
13948 could not acquire lock
13384 could not acquire lock
12116 releasing lock
可以是demonstrated online on tio.run
虽然此程序在 Windows 下工作相同,但此操作系统支持打开未共享的文件,防止其他进程打开。如果不同的进程以这种方式打开文件,我们甚至无法打开它来探测锁定状态。
这不是打开文件的方式,Java,但是,有一个非标准的打开选项可以复制该行为,com.sun.nio.file.ExtendedOpenOption.NOSHARE_WRITE
。在最近的 JDK 中,它位于 jdk.unsupported
模块中。
当我们运行下面扩展测试程序时Windows
import java.io.IOException;
import java.nio.channels.*;
import java.nio.file.*;
import java.util.HashSet;
import java.util.Set;
class LockingWindows {
public static void main(String[] args) throws IOException, InterruptedException {
if(args.length > 0) {
String me = String.format("%6s ", ProcessHandle.current());
Path p = Paths.get(args[0]);
Set<OpenOption> options
= Set.of(StandardOpenOption.WRITE, StandardOpenOption.APPEND);
if(Boolean.parseBoolean(args[1])) options = addExclusive(options);
try(FileChannel fc = FileChannel.open(p, options)) {
FileLock l = fc.tryLock();
if(l == null) System.out.println(me + "could not acquire lock");
else {
System.out.println(me + "got lock");
Thread.sleep(3000);
System.out.println(me + "releasing lock");
l.release();
}
}
}
else {
Path p = Files.createTempFile("lock", "test");
String[] command = {
Paths.get(System.getProperty("java.home"), "bin", "java").toString(),
"-cp", System.getProperty("java.class.path"),
"LockingWindows", p.toString(), "false"
};
ProcessBuilder b = new ProcessBuilder(command).inheritIO();
for(int run = 0; run < 2; run++) {
Process p1 = b.start(), p2 = b.start(), p3 = b.start();
p1.waitFor();
p2.waitFor();
p3.waitFor();
if(run == 0) {
command[command.length - 1] = "true";
b.command(command);
System.out.println("\nNow with exclusive mode");
}
}
Files.delete(p);
}
}
private static Set<OpenOption> addExclusive(Set<OpenOption> options) {
OpenOption o;
try {
o = (OpenOption) Class.forName("com.sun.nio.file.ExtendedOpenOption")
.getField("NOSHARE_WRITE").get(null);
options = new HashSet<>(options);
options.add(o);
} catch(ReflectiveOperationException | ClassCastException ex) {
System.err.println("opening exclusive not supported");
}
return options;
}
}
我们会得到类似
的东西 2356 got lock
6412 could not acquire lock
9824 could not acquire lock
2356 releasing lock
Now with exclusive mode
9160 got lock
Exception in thread "main" java.nio.file.FileSystemException: C:\Users\...\Temp\lock13936982436235244405test: The process cannot access the file because it is being used by another process
at java.base/sun.nio.fs.WindowsException.translateToIOException(WindowsException.java:92)
at java.base/sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:103)
at java.base/sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:108)
at java.base/sun.nio.fs.WindowsFileSystemProvider.newFileChannel(WindowsFileSystemProvider.java:121)
at java.base/java.nio.channels.FileChannel.open(FileChannel.java:298)
at LockingWindows.main(LockingWindows.java:148)
Exception in thread "main" java.nio.file.FileSystemException: C:\Users\...\Temp\lock13936982436235244405test: The process cannot access the file because it is being used by another process
at java.base/sun.nio.fs.WindowsException.translateToIOException(WindowsException.java:92)
at java.base/sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:103)
at java.base/sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:108)
at java.base/sun.nio.fs.WindowsFileSystemProvider.newFileChannel(WindowsFileSystemProvider.java:121)
at java.base/java.nio.channels.FileChannel.open(FileChannel.java:298)
at LockingWindows.main(LockingWindows.java:148)
9160 releasing lock
与您测试结果的相似性表明 Windows 您 运行 与 Java 程序同时使用的程序确实使用了这种模式。
对于您的 Java 程序,只要您不使用该模式,就不会出现此类问题。仅当您必须与另一个未使用协作锁定的 Windows 程序交互时,您才需要处理此问题。