【发布时间】:2019-02-15 01:59:06
【问题描述】:
在以下示例中,我有一个文件被两个线程使用(在实际示例中,我可以有任意数量的线程)
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class A {
static volatile boolean running = true;
public static void main(String[] args) throws IOException, InterruptedException {
String name = "delete.me";
new File(name).deleteOnExit();
RandomAccessFile raf = new RandomAccessFile(name, "rw");
FileChannel fc = raf.getChannel();
Thread monitor = new Thread(() -> {
try {
while (running) {
System.out.println(name + " is " + (fc.size() >> 10) + " KB");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("Interrupted");
Thread.currentThread().interrupt();
}
}
} catch (IOException e) {
System.err.println("Monitor thread died");
e.printStackTrace();
}
});
monitor.setDaemon(true);
monitor.start();
Thread writer = new Thread(() -> {
ByteBuffer bb = ByteBuffer.allocateDirect(32);
try {
while (running) {
bb.position(0).limit(32);
fc.write(bb);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
System.out.println("Interrupted");
Thread.currentThread().interrupt();
}
}
} catch (IOException e) {
System.err.println("Writer thread died");
e.printStackTrace();
}
});
writer.setDaemon(true);
writer.start();
Thread.sleep(5000);
monitor.interrupt();
Thread.sleep(2000);
running = false;
raf.close();
}
}
而不是为每个线程创建一个 RandomAccessFile 和一个内存映射,我在线程之间共享一个文件和一个内存映射,但是有一个问题,如果任何线程被中断,资源就会关闭。
delete.me is 0 KB
delete.me is 2 KB
delete.me is 4 KB
delete.me is 6 KB
delete.me is 8 KB
Interrupted
Monitor thread died
java.nio.channels.ClosedByInterruptException
at java.nio.channels.spi.AbstractInterruptibleChannel.end(AbstractInterruptibleChannel.java:202)
at sun.nio.ch.FileChannelImpl.size(FileChannelImpl.java:315)
at A.lambda$main$0(A.java:19)
at java.lang.Thread.run(Thread.java:748)
Writer thread died
java.nio.channels.ClosedChannelException
at sun.nio.ch.FileChannelImpl.ensureOpen(FileChannelImpl.java:110)
at sun.nio.ch.FileChannelImpl.write(FileChannelImpl.java:199)
at A.lambda$main$1(A.java:41)
at java.lang.Thread.run(Thread.java:748)
有没有什么办法可以防止 FileChannel 仅仅因为一个使用它的线程被中断而被关闭?
编辑我想避免做的是因为我怀疑它不适用于 Java 9+
private void doNotCloseOnInterrupt(FileChannel fc) {
try {
Field field = AbstractInterruptibleChannel.class
.getDeclaredField("interruptor");
field.setAccessible(true);
field.set(fc, (Interruptible) thread
-> Jvm.warn().on(getClass(), fc + " not closed on interrupt"));
} catch (Exception e) {
Jvm.warn().on(getClass(), "Couldn't disable close on interrupt", e);
}
}
顺便说一句,对fc.size() 的调用返回了上述黑客所期望的大小。
【问题讨论】:
-
我虽然:这听起来很有趣但很容易回答。然后我看到了赞成票,你问了这个问题。有些东西告诉我,我什至不必尝试在这里找到答案。现在让真正的大师级别的家伙进来......
-
我能想到的唯一可能的方法是您刚刚在编辑中排除的内容。
-
nio-dev 上已经有很多关于为此引入新的 OpenOption 的讨论,但没有结论。同时,没有任何支持的方式来获取不可中断的 FileChannel。破解中断器字段并不可靠,取决于 JDK 内部,并且随时可能中断。
-
@PeterLawrey 内存映射完全不受文件通道关闭的影响。事实上,我通常会尽早关闭频道,以尽量减少资源使用。例如。
MappedByteBuffer mapped; try(FileChannel fc = FileChannel.open(name, READ, WRITE, CREATE_NEW, DELETE_ON_CLOSE)) { mapped = fc.map(FileChannel.MapMode.READ_WRITE, position, size); } /* use of mapped */请注意,映射缓冲区会阻止DELETE_ON_CLOSE在关闭时立即删除,这会将其变成“退出时删除”行为,我发现这比File.deleteOnExit()更可靠…… -
就像@Holger 所说,即使关闭了创建它的 FileChannel,MappedByteBuffer 仍然有效。事实上,在垃圾收集之前它是有效的。我建议您,内存映射不太适合增长的文件,并且在写入超出边界时会在某些操作系统上给出奇怪的结果。如果您打算将文件的给定部分分配给每个线程,这可能是一个不错的选择,但如果所有线程都需要按顺序写入,您将需要分配足够的字节并检查是否达到边界以创建新文件(或扩展当前文件并映射新创建的部分)。
标签: java multithreading java-9 filechannel