JDK的BIO、伪异步IO、NIO、AIO


JDK1.4推出NIO,1.7提供NIO 2.0。

概述 ★★★

        传统的BIO,建立socket连接之后,读写操作相互阻塞。如果客户端的处理缓慢,会拖慢服务器的处理速度,服务器的并发量就会大幅减少。使用NIO,可以将网络IO等待时间从业务处理线程中抽取出来。比如用一个selector线程管理多个SocketChannel。selector轮询是否有就绪的SocketChannel。

        对于NIO来说,我们的业务线程是在IO操作准备好时,得到通知,接着就有这个线程自行进行IO操作,IO操作本身还是同步的。但是AIO则更进一步,而是在IO操作已经完成后,再给线程发出操作通知。因此AIO是完全不阻塞的。此时我们的业务逻辑就变成一个回调函数,等待IO操作完成后,由系统自动触发。

可以先阅读《Linux中五种I/O模式》。

传统的BIO

        BIO通信模型的服务端,通常由一个独立的Acceptor线程负责监听客户端的连接,接收到客户端请求之后,会为每个客户端创建一个新的线程进行链路处理,处理完成之后,通过输出流返回应答给客户端,线程销毁。


        缺陷是,当客户端访问量增加后,服务端线程的个数和客户端的访问数呈正比,但是JVM线程资源宝贵。

伪异步IO编程

        为了解决同步阻塞IO的一个链路需要一个线程处理的问题,后来引入线程池来处理。将客户端的Socket封装成Task,其实现了Runnable。


        socket的输入流进行读取操作的时候,会一直阻塞,直到有数据或发生异常。伪异步IO仅仅是对之前的一个简单的优化,未从根本上解决同步IO导致的通信线程阻塞问题。

                1、当网络传输慢时,socket读写会阻塞较长时间。其他请求只能在消息队列中排队。
                2、由于线程池采用阻塞队列实现,当队列积满之后,后续入队列的操作将被阻塞。
                3、由于前端只有一个Acceptor线程接收客户端接入,因为线程池队列阻塞,新的请求将被拒绝,客户端会发生大量的连接超时。

NIO编程

可以先阅读《NIO Demo》

    与Socket类和ServerSocket类相对应,NIO也提供了SocketChannel和ServerSocketChannel,这两个通道都支持阻塞和非阻塞的模式。

一些NIO类

1、缓冲区 Buffer

        加入了Bufer是新旧IO的一个重要的区别。在面向流的IO中,可以将数据直接读写到Stream对象中。在NIO库中,所有的数据都是用缓冲区处理的。在读写数据时,它是直接读写到缓冲区中,任何时候访问NIO中的数据,都是通过缓冲区进行操作的。
缓冲区本质上是一个数组,通常是一个字节数组(ByteBuffer),也可以使用其他种类的数组。但是一个缓冲区不仅仅是一个数组,还提供了对数据的结构化访问以及维护读写位置等信息。
Buffer继承关系图


2、通道 Channel

        通道与流的不同之处在于通道是双向的。因为Channel是全双工的,所以可以更好的映射底层操作系统API,特别是在Unix网络模型中,底层操作系统的通道都是全双工的。


3、多路复用器 Selector

        selector会不断轮询注册在其上的channel,如果某个channel上面发生读写事件,也就是就绪状态,会被selector轮询出来,然后通过selectionKey可以获取就绪的channel集合,然后进行后续的IO操作。由于jdk使用了epoll代替传统的select实现,所以没有最大连接句柄1024/2048的限制。所以只需要一个线程负责selector轮询,就可可以接入成千上万的客户端。


        如果发送区TCP缓冲区满,会导致写半包,此时需要注册监听写操作位,循环写,直到整包写入TCP缓冲区。


AIO编程

可以先阅读《AIO Demo》

        提供AsynchronousServerSocketChannel类。NIO2.0引入了新的异步通道的概念,并提供了异步文件通道和异步套接字通道的实现。异步通道提供了以下两种方式获取操作结果。

                1、通过J.U.C.Future类来表示异步操作的结果

                2、在执行异步操作的时候传入一个java.nio,channels    

        completionHandler接口的实现类作为操作完成的回调。NIO2.0是真正的异步非阻塞IO,对应于Unix网络编程中的是时间驱动IO(AIO)。它不需要通过多路复用器(Selecor)对注册的通道进行轮询操作。

        JDK异步回调CompletionHandler的线程执行堆栈:

       JDK底层通过线程池ThreadPoolExecutor来执行回调通知,sun.nio.ch.AsynchronousChannelGroupImpl类实现异步回调通知,它经过层层调用,最终回调com.phei.netty.aio.AsyncTimeClientHandler$1.completed方法,完成回调通知。

        由此我们也可以得出结论:异步Socket Channel是被动执行对象,我们不需要像NIO编程那样创建一个独立的IO线程处理读写操作,对于AsynchronousServerSocketChannel和AsynchronousSocketChannel,它们都由JDK底层的线程池负责回调并驱动读写操作。正因为如此,基于NIO2.0新的异步非阻塞Channel进行编程比NIO编程更简单。

不同IO模型对比


        并不意味着所有的Java网络编程都必须要选择NIO和Netty,具体选择什么样的IO模型或者NIO框架,完全基于业务的实际应用场景和性能诉求,如果客户端并发连接数不多,周边对接的网元不多,服务器的负载也不重,那就完全没必要选择NIO做服务端;如果是相反情况,那就要考虑选择合适的NIO框架进行开发。

猜你喜欢

转载自blog.csdn.net/xuchuangqi/article/details/80616379