AIO、NIO、BIO
一、aio(asynchronous io)
aio是一种异步非阻塞的io模型,异步io是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会产生阻塞,当后台处理完成,操作系统会通知相应的线程进行后续的操作。
服务器实现模式为一个有效请求一个线程,客户端的io请求都是由os先完成了再通知服务器应用去启动线程进行处理。
aio方式适用于连接数多且连接比较长的结构。
二、nio(new io)
nio是一种同步非阻塞的io模型,对于高负载、高并发的网络应用,应使用nio的非阻塞模型来开发。传统io基于字节流和字符流进行操作,而nio基于channel和buffer进行操作,数据从通道读取到缓冲区中或从缓冲区写入通道中。selector用于监听多个通道的事件(如连接打开、数据到达),因此,单个线程可以监听多个数据通道。
服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有io请求时才启动一个线程进行处理。
nio的缓冲区
java io面向流意味着每次从流中读一个或多个字节直至读取所有字节,它们没有缓存在任何地方。此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的数据,需要先将它缓存到一个缓冲区。
nio的缓冲导向方法不同,数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动。增加了处理过程的灵活性。但还需要检查是否该缓冲区中包含所有需要处理的数据。而且,需要确保当更多的数据读入缓冲区时,不能覆盖缓冲区里尚未处理的数据。
nio的非阻塞
nio的非阻塞模式,使一个线程从某个通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有可用数据时,就什么都不会获取,而不是保持线程阻塞,所以直至数据可以读取之前,该线程可以继续做其他事情。非阻塞写也是如此,一个线程请求写入一些数据到某个通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。
nio有三大核心组件,分别是:channel(通道)、buffer(缓冲区)、selector(选择器)。nio是用于连接数目多且比较短的架构。
channel(通道):channel是双向的,既可以用来进行读操作,也可以用来进行写操作。nio中的channel的主要实现有:filechannel、datagramchannel、socketchannel、serversocketchannel分别对应文件io、udp和tcp(server和client)。
buffer(缓冲区):实际上是一个连续的数组。读取或写入的数据都必须经由buffer。在nio中,buffer是一个顶层父类,他是一个抽象类,常用的子类有:bytebuffer、intbuffer、...
selector(选择区):selector类是nio的核心类,selector能够检测多个注册的通道上是否有事件发生,如果有事件发生,便获取事件然后针对每个事件进行相应的相应处理。
1)nio与传统io的区别:
a. nio是非阻塞的,比如说,单线程中从通道读取数据到buffer,同时可以继续做别的事情,当数据读取到buffer后,线程再继续处理数据,写数据也是一样的。io流是阻塞的,当一个线程调用read()或write()时,该线程被阻塞,直到数据完全写入。该线程在此期间不能再干任何事情。
b. nio是面向缓冲区,所有数据都是用缓冲区(buffer)来处理的。在读取数据时,它是直接读到缓冲区中的;在写数据时,写入到缓冲区。io是面向流的,在io流中可以将数据直接写入或者数据直接读到stream对象中,虽然stream中也有buffer开头的扩展类,但只是流的包装类,还是从流读到缓冲区。
c. nio通过通道来读写,通道是双向的,可读可写;而流的读写是单向的。
d. nio有选择器,选择器用于使用单个线程管理多个通道;而io没有。
2)缺点:
jdk的nio底层由epoll实现,该实现饱受诟病的空轮询bug会导致cpu飙升100%。
3)nio如何实现多路复用?
在多路复用io模型中,会有一个线程不断去轮询多个socket的状态,只有当socket真正有读写事件时,才真正调用实际的io读写操作。所以他大大减少了资源占用。在java nio中,是通过selector.select()去查询每个通道是否有到达事件,如果没有到达事件,则一直阻塞在那里,因此这种方式会造成用户线程的阻塞。
另外多路复用io为何比非阻塞io模型的效率高?因为在非阻塞io中,不断轮询socket状态是通过用户线程去进行的,而在多路复用io中,轮询每个socket状态是内核在进行的,这个效率要比用户线程高得多。
三、bio
bio是一种同步阻塞的io模型。数据的读写必须阻塞在一个线程内等待其完成。
服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销。
采用bio通信模型,一般通过在while(true)循环中服务端会调用accept()方法等待接收客户端的连接方式监听请求,一旦接收到连接请求,就可以建立通信套接字进行读写操作,此时不能在接收其他客户端连接请求,只能等待当前连接的客户端操作执行完成。不过可以通过线程池和任务队列来支持多个客户端的连接。
bio方式适用于连接数比较小且固定的架构。