Java NIO学习小结
前面一篇主要学习了下IO的流式操作,接下来就是重头戏了,NIO,又称为New IO
当然也是得抱着问题来学习这个东西了,希望可以通过本文,可以学习到:
- 什么是NIO
- NIO相比较与IO有什么特点
- 同步,非同步,阻塞,非阻塞是什么鬼
- 几种IO模型
I. 基本概念
首先理解下什么是同步IO,非同步IO,什么是阻塞IO,非阻塞IO,它们两对的主要区别是什么;其次就是五种IO模型
1. 同步/非同步IO
a. 同步IO
同步,主要是多个线程的执行中,对彼此的执行结果有依赖,即某个线程的执行,必须要求他依赖的线程二执行完毕
同步IO,表示在发起IO操作之后,如果数据没有准备就绪,就需要用户线程轮询的去询问是否准备好,只有准备好之后,将数据从内核拷贝到用户线程
b. 异步IO
异步,指多个任务可以并发的执行,他们比吃执行结果,是否执行完毕对其他都没有影响
异步IO,用户线程发起IO操作,该用户线程可以继续执行其他的事情;剩下的数据是否准备完毕,准备完毕之后从内核拷贝到用户都有内核自动完成
c. 区别
同步IO和异步IO的主要区别就在于:
- 用户线程发起IO操作之后,是否可以干其他的事情(同步IO需要轮询判断数据是否准备就绪;异步IO不需关心)
- 数据从内核拷贝到用户线程
- 同步IO会阻塞用户线程;异步IO不会
2. 阻塞/非阻塞IO
a. 阻塞IO
阻塞,指在执行过程中,没有获得预期的结果,就一直阻塞等待获取到结果
阻塞IO,在发起IO请求之后,若数据没有准备好,就一直阻塞等待数据准备完毕
b. 非阻塞IO
非阻塞,表示在执行过程中,若某个条件未满足,则直接返回个标识,它继续去干其他的事情
非阻塞IO,在发起IO请求之后,若数据没有准备好,就返回一个对应标识,它继续干其他的事情
3. 五种IO模型
a. 阻塞IO模型
最传统的IO模型,在读写数据时,未准备就绪,则阻塞用户线程,释放CPU资源,当数据准备就绪之后,内核将数据拷贝到用户线程,用户线程取消阻塞状态
b. 非阻塞IO模型
在读写数据时,直接返回结果,如果没有准备好,则自己实现逻辑,轮询的去判断是否准备就绪
当准备完毕之后,再次轮组发起IO请求,就可以将数据拷贝到用户线程
这个过程中,虽然没有释放CPU资源,但是轮询的判断是非常消耗性能的
c. 多路复用IO模型
Java NIO实际上就是多路复用IO。
在多路复用IO模型中,会有一个线程不断去轮询多个socket的状态,只有当socket真正有读写事件时,才真正调用实际的IO读写操作。因为在多路复用IO模型中,只需要使用一个线程就可以管理多个socket,系统不需要建立新的进程或者线程,也不必维护这些线程和进程,并且只有在真正有socket读写事件进行时,才会使用IO资源,所以它大大减少了资源占用
在Java NIO中,是通过selector.select()去查询每个通道是否有到达事件,如果没有事件,则一直阻塞在那里,因此这种方式会导致用户线程的阻塞。
d. 信号驱动IO模型
在发起IO请求时,注册一个信号驱动钩子,然后自己干自己的事情
当数据准备就绪之后,发送一个信号给用户线程,然后用户线程执行自己注册的钩子,在内部实现真实的IO操作
e. 异步IO模型
异步IO模型才是最理想的IO模型,在异步IO模型中,当用户线程发起read操作之后,立刻就可以开始去做其它的事。而另一方面,从内核的角度,当它受到一个asynchronous read之后,它会立刻返回,说明read请求已经成功发起了,因此不会对用户线程产生任何block。然后,内核会等待数据准备完成,然后将数据拷贝到用户线程,当这一切都完成之后,内核会给用户线程发送一个信号,告诉它read操作完成了。也就说用户线程完全不需要实际的整个IO操作是如何进行的,只需要先发起一个请求,当接收内核返回的成功信号时表示IO操作已经完成,可以直接去使用数据了
f. 说明
- 前面四种都属于同步IO(在内核进行数据拷贝都会引起用户线程阻塞),只有最后一个是异步IO
- 异步IO和信号驱动IO的主要区别在于具体的数据处理上
II. NIO
为了解决传统IO的阻塞问题引入的,主要原理如下:
- 一个专门的线程来处理所有的 IO 事件,并负责分发
- 事件驱动机制
- 线程通讯通过 wait/notify 等方式通讯,较少线程切换
1. 基础知识
NIO新定义了三个基本角色:Channel, Buffer, Selector
a. Channel
类似IO中的流,但又有不同
- 支持读写(而流是单向的)
- 与Buffer进行交互(即写入到buffer,从buffer中读取)
- 支持异步
常见的Channel有四种
- FileChannel : 文件,不支持非阻塞方式
- DatagramChannel:UDP网络数据
- SocketChannel:TCP读写网络数据
- ServerSocketChannel:可以监听新进来的TCP连接,像Web服务器那样。对每一个新进来的连接都会创建一个SocketChannel。
b. Buffer
缓冲区,主要有8种:
- ByteBuffer
- CharBuffer
- FloatBuffer
- DoubleBuffer
- IntBuffer
- ShortBuffer
- LongBuffer
- MappedByteBuffer
管道的读写是需要借助Buffer来实现的,一般buffer的读写流程:
- 创建Buffer
ByteBuffer buf = ByteBuffer.allocate(48);
- 写入数据到Buffer
- 从Channel写到Buffer:
channel.read(buf);
- 直接塞入buffer:
buf.put(12);
- 从Channel写到Buffer:
- flip() 切换读写模式
- 将写模式切换到读模式
- 从Buffer读取数据
- 从Buffer读数据到Channel:
channel.write(buf);
- 直接读取数据:
buf.get()
- 从Buffer读数据到Channel:
- 清空缓存区:clear() 或 compact()
- clear 清空,但是数据并未清除,会覆盖
- compact 将所有未读的数据拷贝到Buffer起始处
额外方法:
- Buffer.rewind()
- 将position设回0,所以你可以重读Buffer中的所有数据
- limit保持不变,仍然表示能从Buffer中读取多少个元素(byte、char等)
- Buffer.mark()方法,可以标记Buffer中的一个特定position。之后可以通过调用
- Buffer.reset()方法,恢复到Buffer.mark()标记时的position
c. selector
是Java NIO中能够检测一到多个NIO通道,并能够知晓通道是否为诸如读写事件做好准备的组件
- 创建
Selector selector = Selector.open();
- 注册通道
- 设置通道为非阻塞
channel.configureBlocking(false);
- 注册:
SelectionKey key = channel.register(selector,Selectionkey.OP_READ);
- 设置通道为非阻塞
- 监听事件
- selector.select();当注册的事件到达时,方法返回;否则,该方法会一直阻塞
- 迭代:
selector.selectedKeys().iterator()
- 获取通道:
SelectionKey#channel
- 通过缓冲区读写数据
III. NIO与IO对比
1. Java NIO提供了与标准IO不同的IO工作方式:
- Channels and Buffers(通道和缓冲区):标准的IO基于字节流和字符流进行操作的,而NIO是基于通道(Channel)和缓冲区(Buffer)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。
- Asynchronous IO(异步IO):Java NIO可以让你异步的使用IO,例如:当线程从通道读取数据到缓冲区时,线程还是可以进行其他事情。当数据被写入到缓冲区时,线程可以继续处理它。从缓冲区写入通道也类似。
- Selectors(选择器):Java NIO引入了选择器的概念,选择器用于监听多个通道的事件(比如:连接打开,数据到达)。因此,单个的线程可以监听多个数据通道。
2. 使用场景
NIO
- 优势在于一个线程管理多个通道;但是数据的处理将会变得复杂;
- 如果需要管理同时打开的成千上万个连接,这些连接每次只是发送少量的数据,采用这种;
传统IO
- 适用于一个线程管理一个通道的情况;因为其中的流数据的读取是阻塞的
- 如果需要管理同时打开不太多的连接,这些连接会发送大量的数据;
3. 区别
- IO是面向流的,NIO是面向缓冲区的
- Java IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方;
- NIO则能前后移动流中的数据,因为是面向缓冲区的
- IO流是阻塞的,NIO流是不阻塞的
- Java IO的各种流是阻塞的。这意味着,当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了
- Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取
- 非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。
- 选择器
- Java NIO的选择器允许一个单独的线程来监视多个输入通道,你可以注册多个通道使用一个选择器,然后使用一个单独的线程来“选择”通道
- 这些通道里已经有可以处理的输入,或者选择已准备写入的通道
- 这种选择机制,使得一个单独的线程很容易来管理多个通道。
IV. 其他
参考
- Java NIO:浅析I/O模型
- Java NIO原理图文分析及代码实现
- Java NIO系列教程(一) Java NIO 概述
- Java NIO 与 IO之间的区别
- JAVA IO 以及 NIO 理解
个人博客: Z+|blog
基于hexo + github pages搭建的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛
声明
尽信书则不如,已上内容,纯属一家之言,因本人能力一般,见识有限,如发现bug或者有更好的建议,随时欢迎批评指正
- 微博地址: 小灰灰Blog
- QQ: 一灰灰/3302797840