NIO 概述
前面说到 BIO 有着创建线程多,阻塞 CPU 等问题,因此为解决 BIO 的问题,NIO 作为同步非阻塞 IO模型,随 JDK1.4 而出生了。
在前面我们反复说过4个概念:同步、异步、阻塞、非阻塞。因此,我们就首先用最简单的语言说一下他们的区别,这里,我们心里暂时有个概念即可,在后面的学习过程中,还会对其进行深入的探讨学习。
概念对比
首先,我们先要确立一个概念,就是一个IO操作其实是分为两步的,
- 发起IO请求,即准备数据和区域;
- 实际的IO操作。
而区分一个 IO模型是同步还是异步,就取决于在进行第2个步骤时,即实际进行读写过程中,其是否会阻塞线程,如果会阻塞,那么就是同步的,如果不会(这里就需要操作系统内核来进行操作了),而是完成后,再通知操作线程,那么其就是异步的(AIO 就是 OS 来完成文件读写操作,在完成后通过消息机制来通过线程读写完毕)。
紧接着,区分一个模型是阻塞还是非阻塞,而是看其在数据没有准备完毕时,即连接建立后,但还没有数据过来,请求是需要卡在这等待,还是可以去干其他的事。
作为上文所述的 BIO 来说,首先因为数据是否读写完成,需要其一直在循环里判断,因此其是同步的,并且如果没有数据的话,其会一直卡在 read
操作上,不能继续往下执行,因此 BIO 是一种同步阻塞模型。
而 NIO 的数据读写完成与否,也需要自己来判断,因此也是同步的,但其只有当发生真正读写时,才会进行操作,如果没有准备完毕的话,则可以去做其他的事,因此 NIO 是一个同步非阻塞 IO模型。
总结一下,我们看一个模型是同步还是异步,其实看的是数据准备完毕后的消息通知机制;看是阻塞还是非阻塞的,则是看线程在请求后的线程状态。
下面就让我们来看一下 NIO 的基本概念。
NIO基本介绍
- 从 JDK1.4 后,官方引入的 IO 新特性,称为 NIO,是一个同步非阻塞模型;
- 相关类在
java.nio
包下,有 3大核心组件,Channel,Buffer,Selector; - NIO 是面向缓存区编程的,或者说可以是面向块的,而不是面向字节,因此极大提高了编程的灵活性;
- NIO 因为其核心组件和面向缓存区编程的特性,因此 数据总是从 Channel 读取到 Buffer 中,或者从Buffer 读取到 Channel 中,而 Selector 的作用就是监听多个 Channel,当 Channel 中有请求需要处理,就进行处理;
- 。。。。。(官网或者搜一下,有很多,在这里就不多赘述了)
组件介绍
Buffer
缓存区本质是一个可以读写程序的内存块,我们可以将其简单理解为一个容器。在其内部还提供了一些方法,方便编程人员对其进行操作。
其所有可用子类如 ByteBuffer 等继承的父类为 Buffer,其有一些公有属性需要牢记。
属性 | 说明 |
---|---|
mark | 标记位,在调用 mark() 方法后,可以将当前 position 设为标记,后续在调用 reset() 方法时,就可以将 position 重置回该位置,如果不设置,则为负数,那么在调用 reset 时,会报错。(一般不需要设置) |
position | 缓存区内将要被写或读的节点下标,该值不能为负数,且永远小于等于 limit |
limit | 缓存区内第一个不能写或读的节点下标,该值不能为负数,且永远小于等于 capacity |
capacity | 缓存区被创建时指定的大小,该值不能为负数,且无法更改 |
mark <= position <= limit <= capacity
Buffer 代码演示
IntBuffer intBuffer = IntBuffer.allocate(5);
for (int i = 1; i <= intBuffer.capacity(); i++) {
intBuffer.put(i);
}
// 翻转,读写切换
intBuffer.flip();
while (intBuffer.hasRemaining()) {
System.out.println(intBuffer.get());
}
intBuffer.position(0);
for (int i = 1; i <= intBuffer.capacity(); i++) {
intBuffer.put(i * i);
}
intBuffer.flip();
while (intBuffer.hasRemaining()) {
System.out.println(intBuffer.get());
}
}
从上述代码中,可以看到 缓存区是一个可读可写的区域,且像数组容器一样可以在里面通过下标的方式进行移动,从而修改指定地方内容。
Channel
Channel 是流中的一个组件,在使用过程中,通过流来生成。
Channel 是 NIO 中的一个接口,其实现类中,我们使用的比较多的有FileChannel
,ServerSocketChannel
,SocketChannel
,DatagramChannel
,其中 FileChannel 用于文件的读写,ServerSocketChannel 和 SocketChannel 用于TCP数据读写,DatagramChannel 用于 UDP 数据读写。
这里只描述一些 Channel 的一些简单概念,在后续文章:《文件操作》中,再用代码详细展示 Channel 的作用。
Selector
selector 解决的是 BIO 的线程阻塞和一个请求就需要创建一个线程的问题。selector 可以监控多个 Channel,如果对应的 Channel 有 Event 发生,就可以获取对应 Event,然后根据获取 Event 的不同去进行不同的处理逻辑,这样就不必每个请求都创建线程,并且只有当真正有读写事件发生时,才会进行操作。
也是只描述 Selector 的一些简单概念,在后续文章:《网络编程》中,再用代码详细展示 Selector 的作用。
总结
在本文中,我们初步介绍了 NIO 的概念,以及其的 3个核心组件:Buffer,Channel 和 Selector。
在后续的文章中,我们将对其分别进行介绍,通过 NIO 来逐步引入 Netty 的实现。
本文中代码已上传到 GitHub 上,地址为 https://github.com/wb1069003157/nettyPre-research ,欢迎大家来讨论,探讨。
文章在公众号「iceWang」第一手更新,有兴趣的朋友可以关注公众号,第一时间看到笔者分享的各项知识点,谢谢!笔芯!