为什么要使用Java的AsynchronousFileChannel?

18

我可以理解网络应用程序使用复用技术(以避免创建太多线程),以及程序使用异步调用进行流水线处理(更高效)。但我不理解异步文件通道的效率目的。

有任何想法吗?

4个回答

9
这是一个可以异步读取文件的通道,即I/O操作在单独的线程上完成,因此您调用该方法的线程可以在I/O操作正在进行时执行其他任务。
例如:该类的read()方法返回一个Future对象,以获取从文件中读取数据的结果。因此,您可以调用read(),它会立即返回一个Future对象。在后台,另一个线程将从文件中读取实际数据。您自己的线程可以继续做其他事情,当需要读取数据时,可以在Future对象上调用get()。这将返回数据(如果后台线程尚未完成读取数据,则使您的线程阻塞,直到数据准备好)。这样做的优点是您的线程不必等待整个读取操作的时间;它可以在真正需要数据之前做一些其他事情。
请参见文档
请注意,AsynchronousFileChannel是Java SE 7中的一个新类,该版本尚未发布。

2
谢谢,但我更想知道效率方面的原因——我不明白为什么在另一个线程中执行I/O会有帮助。一个线程总是必须阻塞以进行I/O,并且没有报告的资源重用、硬件特性利用或任何其他增强性能的行为。 - Joey Bell
10
这样做的好处是你 不需要 两个线程。如果你正在写入一大块数据,在低层次上,磁盘控制器会在向磁盘写入数据后发出中断信号,该信号会通过堆栈向上传递以调用完成处理程序。这个过程中你的线程不必阻塞,可以做其他事情。你使用一个更少的线程,线程调度器管理一个更少的线程,你不需要使用Java的notify方法。基本上,你利用更少的资源来完成相同的任务。 - lenkite
3
操作系统还可以优化异步IO操作的执行顺序。例如,如果您有大量对文件中各种区域进行异步读取的操作,则操作系统可以将它们分组成连续的读取操作,而无需担心按照给定的精确顺序执行所有操作。 - Ross Judson
@Ross,是什么阻止了操作系统将来自不同线程的并行请求“分组”?因此,分组并不能解释2倍的性能提升。 - Val
@Jesper,不使用线程等待I/O完成确实可以节省一些资源,但我不相信一个什么也不做的额外线程(它也在并行等待)会消耗Ross Judson报告中所说的一半性能。 - Val
性能提升的原因是你将请求(来自单个线程)进行了流水线处理,因为你不必等待第一个请求完成才发出另一个请求。因此,磁盘控制器将花费更少的时间等待空请求队列,而是更多地执行请求。 - skyde

3
使用异步IO的主要原因是为了更好地利用处理器。假设你有一个应用程序,在文件上进行某种处理,同时假设你可以按块处理文件中包含的数据。如果不使用异步IO,则您的应用程序可能会表现得像这样:
  1. 读取一块数据。此时没有处理器利用率,因为您正在等待数据被读取。
  2. 处理刚刚读取的数据。此时,随着处理数据,您的应用程序将开始消耗CPU周期。
  3. 如果有更多的数据需要读取,则转到#1。
处理器利用率会上升然后下降,然后再上升然后再下降...。理想情况下,如果您希望应用程序高效并尽快处理数据,您不希望处于空闲状态。更好的方法是:
  1. 发出异步读取请求
  2. 当读取完成后,发出下一个异步读取请求,然后处理数据
第一步是引导过程。您还没有数据,因此必须发出读取请求。从那时起,当您收到通知读取已完成后,就发出另一个异步读取请求并处理数据。好处在于,当您完成处理数据块时,下一个读取已经完成,因此您始终有可用的数据来处理,从而更有效地利用处理器。如果您在读取完成之前完成了处理,则可能需要发出多个异步读取请求,以便您有更多数据可供处理。

3
我刚刚发现使用AsynchronousFileChannel的另一个意外原因是,当在NTFS上对大文件执行随机记录导向写入(超过物理内存,因此缓存无法帮助所有内容)时,在单线程模式下,我发现AsynchronousFileChannel执行了两倍于正常FileChannel的操作。
我的猜测是,由于异步io在Windows 7中归结为重叠IO,因此当不必在每个调用之后创建同步点时,NTFS文件系统驱动程序能够更快地更新其自身的内部结构。
我进行了微基准测试,看看RandomAccessFile的性能如何(结果非常接近FileChannel,并且仍然是AsynchronousFileChannel性能的一半)。
不确定多线程写入会发生什么。这是在Java 7上进行的,在SSD上(SSD比磁性快一个数量级,并且在适合内存的较小文件上快一个数量级)。
有趣的是,看看相同的比率是否适用于Linux。

实际上,只要您的文件很短以适应操作系统缓存,无论您的I/O是否同步,I/O都会立即完成。我只想补充一点,当您的I/O和计算时间相当时,最多可以获得2倍的加速。如果计算仅占I/O时间的一小部分或者I/O比计算时间短得可忽略,则重叠它们也没有意义。 - Val

2
这里有一些没人提到的内容:
一个普通的FileChannel实现了InterruptibleChannel,因此它以及使用它的任何东西(例如通过Files.newOutputStream()返回的OutputStream)都有不幸的行为[1][2],即在处于中断状态的线程中执行任何阻塞操作(例如read()write())将导致Channel本身关闭,并引发java.nio.channels.ClosedByInterruptException
如果这是一个问题,使用AsynchronousFileChannel可能是一个可行的替代方案。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接