我的主线程中有一个函数会将一些数据写入磁盘。我不希望我的主线程卡住(磁盘I / O的高延迟)并且创建一个新线程只是为了写入是一个矫枉过正。我决定使用 ExecutorService 。
ExecutorService executorService = Executors.newFixedThreadPool(3);
Future future = executorService.submit(new Callable<Boolean>() {
public Boolean call() throws Exception {
logger.log(Level.INFO, "Writing data to disk");
return writeToDisk();
}
});
writeToDisk 是将写入磁盘的函数
这是一个很好的方式吗?有人可以提出更好的方法吗?
更新:数据大小将大于100 MB。磁盘带宽为40 MBps,因此写入操作可能需要几秒钟。我不希望调用函数卡住,因为它必须做其他工作,所以,我正在寻找一种方法来调度磁盘I / O异步执行调用线程。
我需要委派任务而忘记它!
答案 0 :(得分:7)
无论如何我的代码看起来都很好我从新的非阻塞IO中使用了AsynchronousFileChannel
。该实施使用MappedByteBuffer
到FileChannel
。它可能会给你@Chris所说的表现。以下是一个简单的例子:
public static void main(String[] args) {
String filePath = "D:\\tmp\\async_file_write.txt";
Path file = Paths.get(filePath);
try(AsynchronousFileChannel asyncFile = AsynchronousFileChannel.open(file,
StandardOpenOption.WRITE,
StandardOpenOption.CREATE)) {
asyncFile.write(ByteBuffer.wrap("Some text to be written".getBytes()), 0);
} catch (IOException e) {
e.printStackTrace();
}
}
答案 1 :(得分:2)
我知道有两种方法,启动一个线程(或者使用你拥有的线程池)或内存映射文件并让操作系统管理它。两者都是很好的方法,内存映射文件可以比使用Java编写器/流快10倍,并且它不需要单独的线程,所以当性能是关键时我经常偏向于它。
无论哪种方式,作为优化磁盘写入的一些技巧,尝试尽可能预先分配文件。调整文件大小非常昂贵。旋转磁盘不喜欢寻找,而且SSD不喜欢改变以前写过的数据。
我写了一些基准来帮助我探索这个区域一段时间,随意自己运行benchmarks。其中有一个内存映射文件的例子。
答案 2 :(得分:2)
我同意Õzbek使用非阻塞方法。然而,正如Dean Hiller指出的那样,我们无法使用资源尝试关闭AsynchronousFileChannel
,因为它可能会在写入完成之前关闭通道。
因此,我会在CompletionHandler
上添加asyncFile.write(…,new CompletionHandler< >(){…}
来跟踪完成情况,并在写入操作结束后关闭基础AsynchronousFileChannel
。为了简化使用,我将CompletionHandler
转换为CompletableFuture
,我们可以在写完成后轻松链接并继续执行任何操作。最后的辅助方法是CompletableFuture<Integer> write(ByteBuffer bytes)
,它在完成相应的写操作后返回最终文件索引的CompletableFuture
。
我将所有这些逻辑放在辅助类AsyncFiles中,可以像这样使用:
Path path = Paths.get("output.txt")
AsyncFiles
.write(path, bytes)
.join(); // block if you want to wait for write completion