Java NIO和NIO.2(JSR203)中的非阻塞和异步IO - 反应器/处理器实现

27

所以我正在阅读我最喜欢的软件模式书籍之一(Pattern-Oriented Software Architecture - Patterns for Concurrent and Networked Objects),特别是关于Proactor/Reactor异步IO模式的部分。我可以看到如何使用可选择通道来轻松实现反应器风格的异步IO机制(而且已经这么做了)。但是,我无法看到如何使用非阻塞写实现正确的Proactor机制。也就是利用操作系统管理的非阻塞写函数。

这种功能由操作系统特定调用支持,例如win32下的GetQueuedCompletionStatus

我确实看到Java 7为NIO带来了异步完成处理程序的更新(似乎朝着正确的方向)。话虽如此...鉴于缺乏统一跨平台支持(特别是异步写操作)的OS管理异步操作,我认为这是一种没有利用本地OS支持的拟实现。

所以我的问题是,Proactor基于IO处理在Java中是否有可能以一种对于特定情况有利的方式实现; 如果Java NIO支持基于Proactor的IO处理(无论是在Java 6还是Java 7中),操作系统管理的异步IO支持(即来自操作系统的完成回调)是否被利用?此外,如果实现纯粹在VM中,性能优势是否如此微小,以至于使用主动事件处理提供的仅仅是一种不同(可能更简单)的构建并发网络处理软件的方法。

对于任何对积极事件处理感兴趣的人,这里有一篇很好的文章,概述了优缺点以及与传统的每连接线程和反应器IO模型的比较。


1
如果你真的想知道实现是什么样子,那么你可以下载 JDK 7 的源代码并自己查看:http://openjdk.java.net/projects/jdk7/ - Jesper
好观点,Jesper。我会尽快付出努力去做到这一点! - S73417H
4个回答

23

这个问题涉及到很多因素。我会尽量概括我的发现(注意到有关反应器和前摄器IO处理实现的有争议的有用性)。

Java是否支持基于前摄器的IO处理,以在特定场景下使用时具有优势?

Java 1.4引入了非阻塞IO,但这并不等同于异步IO。Java SE 7通过JSR203引入了异步IO,使“真正”的前摄器风格的IO处理实现成为可能。

参见AsynchronousSocketChannelAsynchronousServerSocketChannel

此外,如果Java NIO确实支持基于前摄器的IO处理(无论是在Java 6还是Java 7中),操作系统管理的异步IO支持(即来自操作系统的完成回调)是否被利用?

通过阅读JSR 203规范,可以确定使用新的异步通道的完成处理程序已得到支持,并且据报告利用了本地操作系统功能,但我还没有确定到什么程度。在分析Java 7源代码之后,我可能会跟进此问题(除非有人比我更快)。

此外,如果实现是纯粹在VM中的,性能利益是否如此之少,以至于使用前摄器事件处理提供的仅仅是构建并发网络处理软件的另一种(可能更简单)方式?

我没有找到有关Java 7中新异步IO特性的性能比较。我相信它们将很快推出。

通常情况下,在解决问题的多种方法中,哪种方法更好的问题几乎总是回答为“取决于”。Java 7包含主动事件处理(使用异步完成处理程序),它不能没有目的地存在。对于某些应用程序,使用这样的IO处理将是有意义的。历史上,一个常见的例子是在HTTP服务器中,其中频繁发出许多短请求时,proactor具有良好的适用性。要了解更详细的解释,请阅读此处内容(仅提供以突出proactor的优点,因此请忽略示例代码为C++的事实)。

在我看来,反应器/主动器在许多情况下会使本来可以使用更传统方法实现非常简单的设计变得复杂,并在其他更复杂的系统中提供高度的简化和灵活性。

. . .

顺便说一句,我强烈建议阅读有关NIO的以下演示文稿,其中提供了NIO和“传统”方法之间的性能比较。尽管我也建议谨慎对待所呈现的结果,因为基准测试中的NIO实现是基于Java 1.4之前的NBIO NIO库而不是1.4中提供的NIO实现。


我在寻找类似于boost::asio的Java库时发现了这个。我的理解是目前没有类似的库,但Java7可能有类似的功能。 - poindexter
1
更新一下:Java 1.4 NIO 在 Windows 上使用 select,而 Java 7 AIO 在 Windows 上使用 IOCP,因此在 Windows 上 AIO 明显更快、更具可扩展性。 - Kr0e

10

我建议你确信需要担心阻塞写入。

读取数据时,当没有数据可读取时会发生阻塞。这种情况通常很普遍。然而,当缓冲区已满时,写操作才会被阻塞。这种情况非常罕见,通常表明连接速度较慢或消费者失败。

如果你想使用非阻塞IO,请为读取操作和写入操作同时进行非阻塞处理。

注意:在NIO中使用阻塞IO通常更简单,而且可能比非阻塞NIO效果更好,除非你有数千个连接,否则增加的复杂性可能不值得(并且可能不是最佳选项)。


虽然对于大多数解决方案来说,反应器(reactor)为基础的IO处理机制最为适用。但在某些情况下,主动式IO处理具有优势(特别是利用底层操作系统提供的抢占式多核线程)。因此,我的问题实际上并不涉及是否需要使用proactor或者它在任何特定场景中是否有优势,而是Java能否成功地实现它而不会变得毫无意义。 - S73417H
1
我怀疑它不太有用的主要原因是,当你比较阻塞式NIO和非阻塞式NIO时,使用阻塞式(每个连接一个线程)IO可以获得显着更好的性能,最多可达1000个连接。我尚未在Windows上进行过测试,但我认为您应该假设可能没有任何性能改进。 - Peter Lawrey

2

我最喜欢的软件模式书之一是《面向模式的软件架构 - 并发和网络对象模式》。

尽管这本书很受欢迎,但它已经过时了,在任何日期都具有可疑的相关性。它出现在 1990 年代末的设计模式狂潮中,当时有人试图将整个计算机科学简化为设计模式。

我的观点是 NIO 已经成为一个框架和设计模式。


在我看来,它仍然很有价值。非阻塞IO是许多工程师害怕或者不了解的东西。这本书展示了一个坚实的C++ NIO设计,今天仍然很强大(Boost/ACE拥有proactor和reactor实现)。尽管可能会听起来像模式追随者,但是reactor和proactor类似的IO处理是每个工程师都应该知道和欣赏的(特别是reactor)。 - S73417H
@S73417H:进一步说,NIO源于select(),而select()则源自一个进程内单线程的设计。现在有一种流派认为,既然我们已经有了线程,就不需要非阻塞/多路复用 I/O了。但是,我曾试图基于Reactor实现通用的NIO框架,我必须强烈反对这是每个工程师都应该了解的东西。NIO和Reactor之间的冲突非常严重,以至于你别无选择,只能坚持使用NIO。在我看来,像Mina这样的NIO框架并不能成为反例。 - user207421
@EJP...我必须尊重地不同意您的观点。除了性能之外,通用NIO框架确实允许在网络编程中更好地分离关注点。根据我的经验,如果您理解并且熟练使用这些框架,开发人员可以创建更易于理解、灵活和可测试的网络应用程序。此外,我无法看出NIO和反应器如何相互冲突。我发现使用Java NIO实现反应器非常简单。此外,在可能同时存在数千个并发网络连接的情况下,使用反应器的线程池表现优异。 - S73417H
1
@S73417H:使用NIO实现Reactor相对简单。难的是在其上构建通用框架。我自己的尝试以及像Mina这样的其他人的尝试并没有鼓舞我。Peter Lawrey在此处或Oracle Java论坛上发布了一项研究结果,反驳了您的最后一点。 - user207421

2
NIO已经提供了响应式模式的实现(选择器),而NIO2增加了主动模式的实现(完成处理程序)。
不要重新发明轮子,只需使用它,因为你无法通过纯Java解决方案来获得其性能 - 这正是任何试图避免阻塞I / O所追求的 - 因为你无法访问底层操作系统的非阻塞/异步特性。但是NIO和NIO2利用了这些功能,使它们快速。

@EJP,我完全同意你的答案,只是想补充一些细节(我刚刚发现的 - 所以我不会忘记)。 - Evgeniy Berezovsky
@EJP OT:我点击了你个人资料中的telekinesis.com.au链接,试图找出你个人资料中的“音乐家”是关于什么的,但是……链接已经失效了!不过这个链接仍然保存在wayback机器中,而你的简历告诉了我比我想知道的更多的信息。我希望它的“健康”部分没有被修改。[回到2007年](http://web.archive.org/web/20091016021123/http://www.telekinesis.com.au/files/114/file/23/EJPitt.pdf),你描述它为:“非常健康;身体素质极佳;不吸烟。”如果我不住在亚洲的北部几英里之外,我会很愿意喝一杯。 - Evgeniy Berezovsky

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