购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

1.1 了解Java中的IO通信

Java中的IO通信在本质上属于网络通信范畴,通俗地讲就是网络之间的数据交互传递。这里需要读者注意的是,IO通信与传统的Java文件读写、Java标准设备输入输出(java.io核心库)操作不是一个概念。本节详细介绍有关于Java IO通信的基础知识。

1.1.1 IO通信基础

在计算机领域提到IO通信,相信大多数读者首先想到的就是Unix网络编程及其所定义的5种I/O模型。同样地,Java IO通信也源自于Unix网络编程所定义的5种I/O模型。

我们知道,网络通信的本质是网络之间的数据IO传递,而在数据传递过程中不可避免地出现“同步/异步”和“阻塞/非阻塞”问题。其实,不仅网络通信会有这个问题,就是本地文件读写也会有同样的问题。那么,什么是“同步/异步”和“阻塞/非阻塞”的概念呢?

1.1.2 “同步/异步”与“阻塞/非阻塞”

本小节详细介绍关于“同步/异步”与“阻塞/非阻塞”这两组概念的异同。

首先,来看一下关于“同步”和“异步”概念的描述。

· 同步就是“请求方”发起一个请求后,“被请求方”在未处理完该请求之前,不向“请求方”返回结果,此时“请求方”肯定也不会接收到“被请求方”的返回结果。

· 异步就是“请求方”发起一个请求后,“被请求方”在得到该请求后,立刻向“请求方”返回相关响应(表示已接收到该请求)。此时,“请求方”已知晓“被请求方”收到了自己发出的请求,但很可能并没有收到返回结果。不过“请求方”并不在意,可以放心继续自己的任务,返回结果会通过事件回调等机制来获取。

由此可见,“异步”相比于“同步”最大的不同就是通过响应而不需要等待返回结果,可以继续自己的任务。

其次,再看一下关于“阻塞”和“非阻塞”概念的描述。

· 阻塞就是“请求方”发起一个请求,然后一直等待“被请求方”返回结果,这期间一直处于“挂起等待”状态,直到返回结果满足条件后才会执行后续任务。

· 非阻塞就是“请求方”发起一个请求,但不用一直等待“被请求方”返回结果,可以先行执行后续任务。

由此可见,“非阻塞”与“阻塞”最大的不同就是不需要一直等待请求返回结果,可以继续执行自己的任务。

此时,读者可能会有些许疑问了,“同步”和“异步”与“阻塞”和“非阻塞”是不是同一个问题的两种描述呢?其实,二者概念还是不同的。

“同步/异步”这组概念主要用于描述“请求-响应”的方式,它们定义到底是“同步”响应方式还是“异步”响应方式。而“阻塞/非阻塞”这组概念主要用于描述返回结果的方式,它们定义到底返回结果是“阻塞”的还是“非阻塞”的。

上述描述看起来比较抽象,我们可以通过一个在快餐店“点餐”的生活情景来形象地解释一下。

第1种情形,当你在柜台点好一份快餐(相当于发出请求),然后就是一直“傻傻地”等在柜台前等餐(相当于接收返回结果),这就是所谓的“同步阻塞”。

第2种情形,还是你在柜台点好一份快餐(相当于发出请求),然后就近找个座位坐下继续自己的事情,并观望柜台上自己的餐是否已经上了,这就是所谓的“同步非阻塞”。

第3种情形,仍旧是你在柜台点好一份快餐(相当于发出请求),同时柜台会发给你一个点餐提醒设备(柜台服务人员会在快餐准备好后,向该设备发送一条提醒信息),然后你大可放心地、随便找个座位坐下继续自己的事情,等待点餐提醒设备的提醒信息就可以了,这就是所谓的“异步非阻塞”。

显然,上述第3种情形的设计最为合理、效率最高,当然在设计上也会相对复杂一些。Java IO通信的原理,基本上也是遵循这3种情形进行设计的。

1.1.3 传统BIO模式

Java IO通信模型中比较传统的就是BIO(Blocking IO)模式,顾名思义就是同步阻塞模式。BIO模式主要用于早期的Java版本,主要特点就是一个请求对应一个应答、弹性伸缩性能比较差。

那么,造成BIO模式性能比较差的原因是什么呢?请参看图1.1中描述的客户端请求服务器响应的过程。

图1.1 传统BIO模式

从图1.1中可以看到,传统BIO模式下的服务器端包含一个接收器(Acceptor),该接收器负责监听每一个客户端的连接请求,并创建相对应的线程来处理该客户端请求。

而传统BIO模式的特点就是,服务器端一旦接收到客户端请求、并通过创建线程来处理该请求,该线程就不会再接收其他的客户端请求了,直到请求处理完成并返回结果(随后销毁该线程)。这就是前文介绍过的,关于“请求-应答”IO通信中最典型的“同步阻塞”方式。

当然,传统BIO模式支持通过多线程来处理多客户端的连接请求。不过,当客户端数量急剧增加时,对应的服务器端线程数量也会按照1:1比例同步增加,势必会占用Java虚拟机中的大量资源,当量变引起质变的时候就会导致系统性能急剧下降(譬如:内存溢出、系统崩溃,等等)。这个问题自然是开发人员不能接受的,于是就有人想到通过人为降低服务器端的线程数量(当然必须满足客户端数量的需求)来解决这个问题。

1.1.4 伪异步IO模式

针对传统BIO模式在性能上的瓶颈问题,Java IO通信模型改进设计了一种伪异步IO模式。简单来讲,就是通过在服务器端控制线程的数量来灵活有效地调配系统线程资源。

为什么要采取控制服务器端的线程数量来解决传统BIO模式的问题呢?最直接的原因就是对于大型应用系统来讲,客户端的数量是无法限制的(而且限制客户端数量的方式也是不符合用户实际需求的)。既然限制客户端数量是行不通的,我们就要想办法通过在服务器端进行合理的控制来达到目的。于是,基于传统BIO模式进行改进的“伪异步IO模式”就出现了。

为了更好地向读者解释伪异步IO模式,请参看图1.2中描述的客户端请求服务器响应的过程。

图1.2 伪异步IO模式

从图1.2中可以看到,数量为N的客户端向服务器端发出连接请求,此时服务器端同样是由接收器(Acceptor)负责监听连接请求,但与传统BIO模式(一个请求对应一个线程)不同的是,服务器端是通过一个任务处理模块Task(主要通过JDK的Runnable接口来实现)来处理这些客户端连接,Task负责将这些连接请求放入一个线程池(Thread Pool)来处理,这个线程池维护着一个消息队列和最大数量为M的活跃线程组(通常N是远大于M的)。在该模式下,由于服务器端负责创建和维护的线程数量可控,因此服务器端占用的资源也是可控的,最大程度地避免了因资源耗尽而导致的系统崩溃问题。

伪异步I/O通信模式的核心就是引入了线程池的概念,从而尽可能地避免了系统线程资源耗尽的问题。但是,该模式在底层仍旧采用了同步阻塞的BIO模型,是无法从根本上解决问题的。

1.1.5 NIO模式

Java NIO(Java Non-blocking IO)模式是在JDK 1.4版本中新引入的一种通信模型。有些人会将“NIO”翻译为“New IO”,这也无可厚非。不过,Java NIO从本质上是一种“同步非阻塞”的通信模式。

Java NIO中包括了三大核心组件:通道(Channel)、轮询器(Selector)和Buffer(缓冲区),是实现“同步非阻塞”模式的关键所在。传统BIO模式下的IO都是基于流(Stream)实现的,而NIO模式下的IO是基于通道(Channel)和Buffer(缓冲区)实现的。简单来讲,NIO操作都是面向通道和缓冲来实现的,数据总是从通道读取到缓冲区中或者从缓冲区写入到通道中,这是与BIO操作最大的不同之处。

那么,NIO如何实现“同步非阻塞”呢?关键就是轮询器(Selector)的使用。轮询器(Selector)负责监视全部通道IO的状态,当其中任意一个或者多个通道具有可用的IO操作时,该轮询器(Selector)会通过一个方法返回大于0的整数,该整数值就表示具体有多少个通道上具有可用的IO操作。服务器正是通过该轮询器(Selector)来完成单事件轮询机制,并实现了多路复用功能。

1.1.6 AIO模式

Java AIO(Java Asynchronous IO)模式是在Java 1.7版本中对NIO模式的一种改进,因此也被称为NIO 2。简单来讲,AIO就是“异步非阻塞”的IO方式。该模式利用了异步IO操作所基于的事件机制和回调机制,实现了服务器后台操作的非阻塞功能,即服务器会在操作完成后通知相应线程进行后续工作。

那么,AIO相比于NIO具体有什么改进呢?虽然NIO提供了非阻塞方法的实现,但本质上NIO的IO操作还是同步的(体现在轮询器Selector操作上)。具体来讲,就是NIO的服务器线程是在IO操作准备好时得到通知,接着就由这个线程自行进行IO操作,因此本质上是同步操作。

AIO模式下是没有轮询器(Selector)功能的,而是在服务器端的IO操作完成后,再给线程发出通知(通过异步回调事件机制)。因此,AIO模式是不会阻塞的,回调操作是在等待IO操作完成后由系统自动触发的。 25UOoErdEXKBdVo15Ou9Uz4K4bagYnCIxQe+WP6jgnpRxEE14TsaArQwb5equtfD

点击中间区域
呼出菜单
上一章
目录
下一章
×