1. IO和NIO的含义
讲NIO之前,我们先来看一下IO。Java IO即Java 输入输出系统。不管我们编写何种应用,都难免和各种输入输出相关的媒介打交道,其实和媒介进行IO的过程是十分复杂的,这要考虑的因素特别多,比如我们要考虑和哪种媒介进行IO(文件、控制台、网络),我们还要考虑具体和它们的通信方式(顺序、随机、二进制、按字符、按字、按行等等)。Java类库的设计者通过设计大量的类来攻克这些难题,这些类就位于java.io包中。
由于老的Java IO标准类提供IO操作(如read(),write())都是同步阻塞的,因此,IO通常也被称为阻塞IO(即BIO,Blocking I/O)。
在JDK1.4之后,为了提高Java IO的效率,Java又提供了一套新的IO(NIO,New I/O),原因在于它相对于之前的I/O类库是新增的。由于之前老的I/O类库是阻塞I/O,New I/O类库的目标就是要让Java支持非阻塞I/O,所以,更多的人喜欢称之为非阻塞I/O(Non-block I/O)。
在此种方式下,用户进程在发起一个 IO 操作以后,必须等待 IO 操作的完成,只有当真正完成了 IO 操作以后,用户进程才能运行。 JAVA传统的 IO 模型属于此种方式!
在此种方式下,用户进程发起一个 IO 操作以后 便可返回做其它事情,但是用户进程需要时不时的询问 IO 操作是否就绪,这就要求用户进程不停的去询问,从而引入不必要的 CPU 资源浪费。其中目前 JAVA 的 NIO 就属于同步非阻塞 IO 。
此种方式下是指应用发起一个 IO 操作以后,不等待内核 IO 操作的完成,等内核完成 IO 操作以后会通知应用程序,这其实就是同步和异步最关键的区别,同步必须等待或者主动的去询问 IO 是否完成,那么为什么说是阻塞的呢?因为此时是通过 select 系统调用来完成的,而 select 函数本身的实现方式是阻塞的,而采用 select 函数有个好处就是它可以同时监听多个文件句柄,从而提高系统的并发性!
在此种模式下,用户进程只需要发起一个 IO 操作然后立即返回,等 IO 操作真正的完成以后,应用程序会得到 IO 操作完成的通知,此时用户进程只需要对数据进行处理就好了,不需要进行实际的 IO 读写操作,因为 真正的 IO读取或者写入操作已经由 内核完成了。目前 Java 中还没有支持此种 IO 模型。
所有的系统I/O都分为两个阶段:等待就绪和操作。举例来说,读函数,分为等待系统可读和真正的读;同理,写函数分为等待网卡可以写和真正的写。Java IO的各种流是阻塞的。这意味着当线程调用write()或read()时,线程会被阻塞,直到有一些数据可用于读取或数据被完全写入。
需要说明的是等待就绪引起的“阻塞”是不使用CPU的,是在“空等”;而真正的读写操作引起的“阻塞”是使用CPU的,是真正在"干活",而且这个过程非常快,属于memory copy,带宽通常在1GB/s级别以上,可以理解为基本不耗时。因此,所谓“阻塞”主要是指等待就绪的过程。
以socket.read()为例子:
传统的阻塞IO(BIO)里面socket.read(),如果TCP RecvBuffer里没有数据,函数会一直阻塞,直到收到数据,返回读到的数据。
而对于非阻塞IO(NIO),如果TCP RecvBuffer有数据,就把数据从网卡读到内存,并且返回给用户;反之则直接返回0,永远不会阻塞。
换句话说,BIO里用户最关心“我要读”,NIO里用户最关心"我可以读了"。NIO一个重要的特点是:socket主要的读、写、注册和接收函数,在等待就绪阶段都是非阻塞的,真正的I/O操作是同步阻塞的(消耗CPU但性能非常高)。
NIO通道基础
通道使用本地代码执行实际工作。通道接口允许我们以便携和受控的方式访问低级I/O服务。在层次结构的顶部,通道接口如下所示:package java.nio.channels;
public interface Channel{
public boolean isOpen();
public void close() throws IOException;
}
正如在上述通道接口中看到的,所有通道中有常见的两个操作:
创建选择器
可以通过调用Selector.open()
方法创建一个选择器,如下代码所示:
Selector selector = Selector.open();
一个socket连接对应一个输入/输出通道,一个输入/输出通道在对应一个线程,这样有多个请求,就需要开辟多个线程,这样对CPU的负担大大的提高。关系是一对一的。
在上图中我们能看到中间有类叫做Selector
所以我们能看出不必像原先那样开大量的线程,然后让线程傻傻的等待。而NIO不必,通道已经由一个线程建立好了,有事就说话,说了就去做,而且在一个线程中建立了多个通道,可以同时做很多事情,不会像原先那样开辟多个线程,耗费CPU资源,关系是1对多的。
IO是面向流的,NIO是面向缓冲区的
IO流是阻塞的,NIO流是不阻塞的
- Java IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方;
- NIO则能前后移动流中的数据,因为是面向缓冲区的;
- Java IO的各种流是阻塞的。这意味着,当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了
- Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取。NIO可让您只使用一个(或几个)单线程管理多个通道(网络连接或文件),但付出的代价是解析数据可能会比从一个阻塞流中读取数据更复杂。
- 非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。
选择器
- Java NIO的选择器允许一个单独的线程来监视多个输入通道,你可以注册多个通道使用一个选择器,然后使用一个单独的线程来“选择”通道:这些通道里已经有可以处理的输入,或者选择已准备写入的通道。这种选择机制,使得一个单独的线程很容易来管理多个通道。
NIO
参考文献:
http://ju.outofmemory.cn/entry/6835
https://www.yiibai.com/java_nio/java-nio-vs-input-output.html