Java的I/O模型
来聊一聊Java的BIO和NIO及一些底层原理,有时间下期更新Netty相关内容~
模型基本说明
BIO:同步并阻塞(传统阻塞型)
- 服务器为每一个新连接建立一个新线程进行处理,如果连接没有数据也会阻塞等待(不做任何事情)造成不必要的线程开销(示意图如下) PS:BIO -> blocking io
现在Java的I/O模型
NIO:同步非阻塞
- 服务器的一个线程能处理多个请求(连接),客户端发送的连接请求会注册到多路复用器上,多路复用器轮询到连接有I/O请求就进行处理(示意图如下) PS:NIO -> non-blocking io
AIO:异步非阻塞(非重点)
引入了异步通道的概念,采用Proactor模式,简化了程序编写,有效的请求才会启动线程,它的特点是先由操作系统完成后才通知服务端程序启动线程去处理,一般适用于连接数较多且连接时长较长的应用
BIO、NIO、AIO使用场景分析
BIO适用于连接数小且固定的架构,对服务器资源占用高,一般只在JDK1.4以前(唯一选择)
NIO适用于连接数目多且连接比较短的架构,如聊天服务器,弹幕系统,编程复杂,JDK1.4开始才有
AIO适用于连接数目多且连接比较长的架构,如相册服务器,充分调用OS参与并发操作,JDK1.7后出现
Java BIO 编程
BIO基本介绍
Java BIO就是传统的Java I/O编程,其相关类和接口在 java.io
BIO(Blocking IO):同步阻塞,服务器为每个连接请求建立新的线程,如果没有事情做也会阻塞等待,直到断开链接,这样会造成不必要的线程开销
对于BIO的架构可以通过线程池机制改善(多个客户连接服务器)
BIO占用资源高,适用于连接数比较小且固定的架构,但是代码简单
Java BIO 工作机制
BIO工作流程
每个请求都需要创建独立的线程,为客户端数据Read/Write
当并发数大的时候,要创建大量线程来处理链接,系统资源占用大
建立连接后,线程如果暂时没有数据可读,就阻塞在Read上,造成线程资源浪费
Java NIO 编程
NIO基本介绍
Java NIO 全称 Java non-blocking IO是JDK1.4开始提供的新API,是同步非阻塞的
NIO相关类都放在java.nio包下,并对原java.io包中的很多类进行改写
NIO有三大核心部分:Channel(通道)、Buffer(缓冲区)、Selector(选择器)
NIO是面向缓冲区,或者面向块编程的,不是简单的read和write,数据读取到一个它稍后处理的缓冲区,也可按需要在缓冲区前后移动读取数据
NIO的非阻塞模式:一个线程从某通道读取数据的时候,仅能得到目前可用的数据,当没有可用的数据时,什么都不会获取,会去做别的事情(因为数据是块的,所以等它攒到一定大小直到可用的时候再给就行,而不会因为数据是散装的要一直阻塞等待浪费线程!!!)
这样就能将很多请求交由一个线程处理,大幅度减少线程开销
NIO和BIO
BIO以流的形式处理数据,NIO以块的形式处理数据
BIO是阻塞的,NIO是非阻塞的
BIO的流是字节流和字符流,而NIO的块基于Channel(通道)和Buffer(缓冲区)读取数据,Selector(选择器)监听多个通道的事件(连接请求,数据到达)
NIO三大核心
关系图
每一个Channel都会对应一个Buffer
Selector对应一个服务器线程,一个线程对应多个Channel
Selector选择哪个是由Channel的事件Event决定的
Selector会根据事件安排在各个通道上切换
Buffer是缓冲区,其实就是一个内存块,底层就是一个数组(bytebuffer)
Buffer的读写是双向,既可以读也可以写,像底层操作系统的系统通道也是双向的
缓冲区(Buffer)
Buffer的本质 -> 可以读写的数据内存块(容器对象)
数据的读写必须经过buffer
Buffer类的基本信息
Buffer类相关方法(比较灵活)
通道(Channel)
NIO通道与BIO的流的区别
- 通道可以同时进行读写,流只能读或者写
- 通道可以异步读写数据,流只能同步
- 通道可以从缓冲区读&写数据
常用Channel类
- BIO的Stream是单向的,NIO的Channel是双向的
- 常用的Channel类:ServerSocketChannel(类似ServerSocket)和SocketChannel(类似Socket)
- Filechannel ->文件读写通道
- DatagramChannel -> UDP数据读写通道
- ServerSocketChannel & SocketChannel -> TCP读写通道
Selector(选择器)
- Java的NIO,用非阻塞IO模式,得益于Selector
- 多个Channel以事件的方式注册到同一个Selector -> 这样Selector可监控多个通道是否有事件发生 -> 一个线程管理多个通道的连接和请求
- 只有有事件发生时才回去读写,减少线程开销(线程上下文切换)
- 线程在非阻塞IO的空闲时间在其他有需要的通道上执行IO操作,节约了时间
- Netty的IO线程NioEventLoop(事件)
示意图
参考资料
- 尚硅谷Netty【韩顺平】:https://www.bilibili.com/video/BV1DJ411m7NR
- 推荐一个I/O形象解释:https://blog.csdn.net/szxiaohe/article/details/81542605