9.1 为什么要使用非阻塞式I/O

在介绍并行化处理时,讲了很多关于如何高效利用多核CPU的内容。这种方式很管用,但在处理大量数据时,它并不是唯一可用的线程模型。

假设要编写一个支持大量用户的聊天程序。每当用户连接到聊天服务器时,都要和服务器建立一个TCP连接。使用传统的线程模型,每次向用户写数据时,都要调用一个方法向用户传输数据,这个方法会阻塞当前线程。

这种I/O方式叫阻塞式I/O,是一种通用且易于理解的方式,因为和程序用户的交互通常符合这样一种顺序执行的方式。缺点是,将系统扩展至支持大量用户时,需要和服务器建立大量TCP连接,因此扩展性不是很好。

非阻塞式I/O,有时也叫异步I/O,可以处理大量并发网络连接,而且一个线程可以为多个连接服务。和阻塞式I/O不同,对聊天程序客户端的读写调用立即返回,真正的读写操作则在另一个独立的线程执行,这样就可以同时执行其他任务了。如何使用这些省下来的CPU周期完全取决于程序员,可以选择读入更多数据,也可以玩一局Minecraft游戏。

到目前为止,我避免使用代码来描述这两种I/O方式,因为根据API的不同,它们有多种实现方式。Java标准类库的NIO提供了非阻塞式I/O的接口,NIO的最初版本用到了Selector的概念,让一个线程管理多个通信管道,比如向客户端写数据的网络套接字。

然而这种方式压根儿就没有在Java程序员中流行起来,它编写出来的代码难于理解和调试。引入Lambda表达式后,设计和实现没有这些缺点的API就顺手多了。