Java的I/O发展简史
从JDK1.0到JDK1.3,Java的I/O类库都非常原始,很多UNIX网络编程中的概念或者接口在I/O类库中都没有体现,比如Pipe、Channel、Buffer和Selector等。
2002年发布JDK1.4时,NIO以JSR-51的身份正式随JDK发布。它新增加了java.nio包,提供了很多进行异步I/O开发的API和类库。
新的NIO类库的提供,促进了基于Java的异步非阻塞编程的发展和应用,但是,它对文件系统的处理能力仍显不足,主要问题如下:
- 没有统一的文件属性(例如读写权限)
- API能力比较弱,例如目录的级联创建和递归遍历,往往需要自己实现
- 底层存储系统的一些高级API无法使用
- 所有的文件操作都是同步阻塞调用,不支持异步文件读写操作
2011年7月28日,JDK1.7正式发布。将原来的NIO类库进行了升级,被称为NIO2.0。NIO2.0由JSR-203演进而来,主要提供了三个方面的改进:
- 提供批量获取文件属性的API,具有平台无关性,不与特性的文件系统耦合,提供了标准文件系统的SPI,供各个服务提供商扩展实现
- 提供了AIO功能,支持基于文件的异步I/O操作和针对网络套接字的异步操作
- 完成JSR-51定义的通道功能,包含对配置和多播数据报等的支持
Java的I/O模型
同步阻塞式I/O(BIO)
采用BIO通信模型的服务端,通常由一个独立的Acceptor线程负责监听客户端的连接,它接收到客户端连接请求之后为每个客户端创建一个新的线程进行链路处理,处理完成之后,通过输出流返回应答给客户端,线程销毁。这就是典型的一请求一应答通信模型。
这种模型,缺乏弹性伸缩能力,客户端并发访问量增加后,服务端线程个数与客户端并发数呈1:1,线程膨胀后,系统性能急剧下降,并发量增大可能导致线程堆栈溢出、创建新线程失败等问题,最终导致进程宕机或者僵死,不能对外提供服务。
为了解决BIO的问题,通过线程池和任务队列,优化出来一种伪异步I/O的模型。
当有新的客户端接入的时候,将客户端的Socket封装成一个Task(该任务实现java.lang.Runnable接口)投递到后端的线程池中进行处理,JDK的线程池维护一个消息队列和N个活跃线程对消息队列中的任务进行处理。由于线程池可以设置消息队列的大小和最大线程数,因此,它的资源占用是可控的,无论多少个客户端并发访问,都不会导致资源的耗尽和宕机。
伪异步I/O实际上仅仅只是对之前I/O线程模型的一个简单优化,它无法从根本上解决同步I/O导致的通信线程阻塞问题。如果通信对方返回应答时间过长,会引起的级联故障。
- 服务端处理缓慢,返回应答消息耗费60s,平时只需要10ms
- 采用伪异步I/O的线程正在读取故障服务节点的响应,由于读取输入流是阻塞的,因此,它将会被同步阻塞60s
- 假如所有的可用线程都被故障服务器阻塞,那后续所有的I/O消息都将在队列中排队
- 由于线程池采用阻塞队列实现,当队列积满之后,后续入队列的操作将被阻塞
- 由于前端只有一个Accptor线程接收客户端接入,它被阻塞在线程池的同步阻塞队列之后,新的客户端请求消息将被拒绝,客户端会发生大量的连接超时
- 由于几乎所有的连接都超时,调用者会认为系统已经崩溃,无法接收新的请求消息
同步非阻塞式I/O(NIO)
NIO的包含的一些概念:
- 进行异步I/O操作的缓冲区ByteBuffer等
- 进行异步I/O操作的管道Pipe
- 进行各种I/O操作(异步或者同步)的Channel,包括ServerSocketChannel和SocketChannel
- 多种字符集的编码能力和解码能力
- 实现非阻塞I/O操作的多路复用器Selector
- 基于流行的Perl实现的正则表达式类库
- 文件通道FileChannel
NIO服务端序列图
NIO客户端序列图
NIO代码比较复杂,使用NIO编程的优点如下:
- 客户端发起的连接操作是异步的,可以通过在多路复用器注册OP_CONNECT等待后续结果,不需要像之前的客户端那样被同步阻塞。
- SocketChannel的读写操作都是异步的,如果没有可读写的数据它不会同步等待,直接返回,这样I/O通信线程就可以处理其他的链路,不需要同步等待这个链路可用
- 线程模型的优化:由于JDK的Selector在Linux等主流操作系统上通过epoll实现,它没有连接句柄数的限制(只受限于操作系统的最大句柄数或者对单个进程的句柄限制),这意味着一个Selector线程可以同时处理成千上万个客户端连接,而且性能不会随着客户端的增加而线性下降,因此,它非常适合做高性能、高负载的网络服务器
异步非阻塞式I/O(AIO)
NIO2.0引入了新的异步通道的概念,并提供了异步文件通道和异步套接字通道的实现。异步通道提供两种方式获取获取操作结果。
- 通过java.util.concurrent.Future类来表示异步操作的结果;
- ◎在执行异步操作的时候传入一个java.nio.channels。
CompletionHandler接口的实现类作为操作完成的回调。
NIO2.0的异步套接字通道是真正的异步非阻塞I/O,它对应UNIX网络编程中的事件驱动I/O(AIO),它不需要通过多路复用器(Selector)对注册的通道进行轮询操作即可实现异步读写,从而简化了NIO的编程模型。
总结
不同的I/O模型由于线程模型、API等差别很大,所以用法的差异也非常大。几种I/O模型的功能和特性对比如下表所示:
标签:异步,Java,NIO,队列,模型,阻塞,线程,客户端 From: https://blog.csdn.net/qq_34149443/article/details/139706233