Java NIO 基础

来源:互联网 发布:淘宝一元包邮的店 编辑:程序博客网 时间:2024/06/14 04:05

Java 1.4 之前的版本,对I/O的支持不是很完善,在开发高性能的I/O程序时,主要面临以下问题。

1、没有数据缓冲区,I/O性能存在问题;

2、只有输入和输出流,没有C或者C++中的Channel概念;

3、同步阻塞式I/O通信(BIO),通常会导致通信线程被长时间阻塞;

4、支持的字符集有限,硬件可移植性不好。

下面对Java I/O的演进过程做一个简单梳理。

网络编程的基本模型是Client / Server 模型,即两个进程之间相互通信。服务端提供位置信息(绑定IP地址和监听端口),客户端通过连接操作向服务端监听的地址发起连接请求,通过三次握手建立连接。如果连接建立成功,双方就可以通过网络套接字(socket)进行通信。

BIO通信模型

采用BIO通信模型的服务端,通常由一个独立的Acceptor线程负责监听客户端的连接,它接受到客户端连接请求之后为每个客户端创建一个新的线程进行链路处理,处理完成之后,通过输出流返回应答给客户端,线程销毁。

该模型最大的问题就是缺乏弹性伸缩能力,当客户端并发访问量增加之后,服务端的线程个数和客户端并发访问数呈1:1的正比关系,由于线程是Java虚拟机非常宝贵的系统资源,当线程数膨胀之后,系统的性能将急剧下降,随着并发访问量的继续增大,系统会发生线程堆栈溢出、创建新线程失败等问题,并最终导致进程宕机或者僵死,不能对外提供服务。

同步阻塞式I/O

客户端通过Socket创建,发送查询时间服务器的“QUERY TIME ORDER”指令,然后读取服务端的响应并将结果打印出来,随后关闭连接,释放资源,程序退出执行。

伪异步I/O编程

为了改进一线程一连接模型,后来又演进出了一种通过线程池或者消息队列实现一个或者多个线程处理N个客户端的模型,由于它的底层通信机制依然使用同步阻塞I/O,所以被称为“伪异步”。后端通过一个线程池来处理多个客户端的请求接入,形成客户端个数M:线程池最大线程数N的比例关系,其中M可以远远大于N。通过线程池可以灵活地调配线程资源,设置线程的最大值,防止由于海量并发接入导致线程耗尽。

伪异步的I/O通信框架是通过采用线程池和任务队列实现的。当有新的客户端接入时,将客户端的socket封装成一个task(该task实现runnable接口)投递到后端的线程池中进行处理。JDK的线程池可以维护一个消息队列和N个活跃线程,对消息队列中的任务进行处理。由于线程池可以设置消息队列的大小和最大线程数,因此,它的资源占用是可控的,无论多少个客户端并发访问,都不会导致资源的耗尽和宕机。

伪异步I/O通信框架采用了线程池实现,因此避免了为每个请求都创建一个独立线程造成的线程资源耗尽问题。但是由于它底层的通信依然采用同步阻塞模型,因此无法从根本上解决问题。如果通信对方返回应答时间过长会引起以下级联故障:

1)服务端处理缓慢,返回应答消息耗费60s,平时只需要10ms。

2)采用伪异步I/O的线程正在读取故障服务节点的响应,由于读取输入流是阻塞的,它将会被同步阻塞60s。

3)假如所有的可用线程都被故障服务器阻塞,那后续所有的I/O消息都将在队列中排队。

4)由于线程池采用阻塞队列实现,当队列积满之后,后续入队列的操作将被阻塞。

5)由于前端只有一个Acceptor线程接收客户端接入,它被阻塞在线程池的同步阻塞队列之后,新的客户端请求消息将被拒绝,客户端会发生大量的连续超时。

6)由于几乎所有的连接都超时,调用者会认为系统已经崩溃,无法接受新的请求消息。

待续。。。

原创粉丝点击