包括BIO(阻塞式),NIO(非阻塞式),AIO(异步)相关的介绍与比较.
todo: 实现代码demo,加深理解
相关概念
阻塞/非阻塞IO
: 数据没有就绪时等待
/直接返回
同步/异步IO
: 关键在于数据拷贝阶段是由用户线程
还是内核
完成(因此异步IO必须有操作系统的底层支持)
IO可以分为两个阶段: 等待就绪
与操作
.
可以看出来,阻塞/非阻塞反映在第一阶段,同步/异步则反映在第二阶段.
NIO/AIO中相关概念
Buffer
(缓冲区): 包含一些要写入或读出的数据,在nio/aio里,所有数据都是用缓冲区处理的.Channel
(通道): 类似于stream(流),但通道是双向的,可以同时读写(另外底层操作系统的通道一般都是全双工的,因此channel比stream更适合进行描述)Selector
(多路复用器): 可以在Selector上注册Channel,在NIO中Selector会不断轮询获取就绪的Channel(其上有读或写事件)(并可以通过SelectionKey
可以获取就绪的channel集合进行后续操作)event dispatcher
(事件分发器): 将事件源分发给处理者(回调函数)
四种IO模式介绍
BIO(Blocking IO)
传统的BIO即阻塞式IO编程模型,这种方式通常在接受一个新连接后,就开一个新线程去同步阻塞式地处理请求,
从代码角度看就是socket.accept()
, socket.read()
,socket.write()
这几个方法都是同步阻塞的.
这种方式的缺点很明显:
- 连接增多时,会创建大量线程,每个线程都会占用不小的内存资源
- 线程上下文切换的代价是昂贵的,大量线程会导致大量的切换,但因为大量线程处于阻塞状态(切进来没做任意事又切出去),因此会存在大量无意义的切换.造成cpu负载很高,但工作效率很低.
伪异步IO
即使用线程池,将新建线程改为提交到线程池里,实现N连接:M线程
(N可以远大于M).
但这种方式底层还是使用的同步阻塞IO,当连接超过线程池上限时,其它连接只能等待.
当数据量大,网络慢时,一个连接仍然会长时间占用线程.
NIO(Non-Blocking IO)
java新的NIO模型即非阻塞式IO编程模型,里面使用了事件模型
.
event dispatcher(事件分发器)
的模式为: Reactor(反应器)
,此模式基于同步IO,
就是在读写就绪了后,分发器会通知回调处理者,再由处理者自行去读写(这个读写是同步阻塞的)
NIO2/AIO(Async IO)
java的nio2.0引用了新的AIO概念,即异步IO.
event dispatcher(事件分发器)
的模式为: Proactor
,此模式基于异步IO,
就是在读写就绪了后,分发器会给系统底层发送读写请求,并且在读写完成之后,
再通知回调处理者(这才是系统级别的,真正的异步)
四种IO模式比较
IO模式 | BIO | 伪异步IO | NIO | AIO |
---|---|---|---|---|
连接数:IO线程 | 1:1 | N:M(N>=M) | N:1 | N:0 |
IO阻塞类型 | 阻塞 | 阻塞 | 非阻塞 | 非阻塞 |
IO同步类型 | 同步 | 同步 | 同步(多路复用) | 异步 |
API使用难度 | 简单 | 简单 | 复杂 | 一般 |
调试难度 | 简单 | 简单 | 复杂 | 复杂 |
可靠性 | 非常差 | 差 | 高 | 高 |
吞吐量 | 低 | 中 | 高 | 高 |
其中NIO与AIO主要差别就在于AIO会在读写完成后再通知处理者,而NIO会在读写就绪时通知处理者去读取 (虽然这个读取是同步阻塞的,但通常非常快,属于memory copy,基本上不耗时,因此效率上NIO与AIO差别可能不会很大)