【问题标题】:Java: Asynchronous concurrent writes to diskJava:异步并发写入磁盘
【发布时间】:2014-11-13 07:53:46
【问题描述】:

我的主线程中有一个函数可以将一些数据写入磁盘。我不希望我的主线程卡住(磁盘 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 与调用线程的执行异步。

我需要委派任务并忘记它!

【问题讨论】:

  • 这个问题可能过于宽泛,因为您对您的确切要求仍然非常不明确。例如,我们对您的磁盘配置和正在写入的块的大小一无所知。
  • 你知道AsynchronousFileChannel吗?

标签: java multithreading java.util.concurrent


【解决方案1】:

不管怎样,我从新的非阻塞 IO 中使用了 AsynchronousFileChannel,您的代码看起来不错。该实现使用MappedByteBufferFileChannel。它可能会给你@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 用于记住这个 api。但是,请测量其性能。上次我调查 AsynchronousFileChannel 时,它是使用单独的线程在幕后实现的。因此与自己创建线程等效(但可能更方便)。但这当然可以随着 JVM 的版本而改变。
  • hmmmm,如果它是异步的,尝试使用资源使其在完成之前关闭似乎有点不对劲? main() 方法应该在 Future 返回完成之前退出(否则,它只是同步的)
  • @DeanHiller 我想这是 not 在这种情况下使用 try-with-resource 的一个很好的理由:AsynchronousFileChannel 可以被视为较低级别的对象,当“资源”将是更高级别的(因此访问器更容易)。
【解决方案2】:

我知道有两种方法,启动线程(或使用线程池)或内存映射文件并让操作系统管理它。两者都是很好的方法,内存映射文件可以比使用 Java 编写器/流快 10 倍,而且它不需要单独的线程,所以当性能很关键时,我经常倾向于这样做。

无论哪种方式,作为优化磁盘写入的一些技巧,请尝试在可能的情况下预分配文件。调整文件大小是昂贵的。旋转磁盘不喜欢搜索,SSD 不喜欢改变之前写入的数据。

不久前我写了一些基准来帮助我探索这个领域,请随时自行运行benchmarks。其中有一个内存映射文件的例子。

【讨论】:

  • 对不起,我对内存映射写入没有任何经验,这是否意味着我将简单地将数据结构与文件链接并继续写入? OS(或JVM?)会在闲暇时将其简单地写入磁盘吗?如果是的话,看起来很方便。
  • @MangatRai 是正确的。内存映射文件连接到操作系统和硬件的内存管理部分,使写入内存等同于写入磁盘。这确实意味着您必须乐于使用字节,并且损坏的磁盘将导致整个进程终止(没有 IOException)。然而,优点是速度、便利性以及在进程死亡后仍能保留数据和快速进程间通信的能力(不是你需要的)。
【解决方案3】:

我同意Õzbek 使用非阻塞方法。然而,正如 Dean Hiller 所指出的,我们不能在使用资源尝试之前关闭AsynchronousFileChannel,因为它可能会在写入完成之前关闭通道。

因此,我将在asyncFile.write(…,new CompletionHandler&lt; &gt;(){…} 上添加一个CompletionHandler 以跟踪完成并在写操作结束后关闭底层AsynchronousFileChannel。为了简化使用,我将CompletionHandler 变成了CompletableFuture,我们可以轻松地将它与一个延续链接起来,以便在写入完成后做任何我们想做的事情。最终的辅助方法是CompletableFuture&lt;Integer&gt; write(ByteBuffer bytes),它在完成相应的写操作后返回最终文件索引的CompletableFuture。 我将所有这些逻辑放在一个辅助类AsyncFiles 中,可以这样使用:

Path path = Paths.get("output.txt")
AsyncFiles
    .write(path, bytes)
    .thenAccept(index -> /* called on completion from a background thread */)

【讨论】:

    猜你喜欢
    • 2018-11-07
    • 2013-03-25
    • 2017-04-24
    • 1970-01-01
    • 1970-01-01
    • 2011-04-05
    • 2014-10-30
    • 2013-01-02
    相关资源
    最近更新 更多