JDK中关于BIO,NIO,AIO,同步,异步介绍

cover

本文原创地址,我的博客https://jsbintask.cn/2019/04/16/essay/nio/(食用效果最佳),转载请注明出处!

在理解什么是BIO,NIO,AIO之前,我们首先需要了解什么是同步,异步,阻塞,非阻塞。假如我们现在要去银行取钱:
同步 : 自己亲自出马持银行卡到银行取钱(使用同步IO时,Java自己处理IO读写);
异步 : 委托一小弟拿银行卡到银行取钱,然后给你(使用异步IO时,Java将IO读写委托给OS处理,需要将数据缓冲区地址和大小传给OS(银行卡和密码),OS需要支持异步IO操作API);
阻塞 : ATM排队取款,你只能等待(使用阻塞IO时,Java调用会一直阻塞到读写完成才返回);
非阻塞 : 柜台取款,取个号,然后坐在椅子上做其它事,等号广播会通知你办理,没到号你就不能去,你可以不断问大堂经理排到了没有,大堂经理如果说还没到你就不能去(使用非阻塞IO时,如果不能读写Java调用会马上返回,当IO事件分发器会通知可读写时再继续进行读写,不断循环直到读写完成)

BIO

Blocking IO,同步阻塞式IO,jdk1.4以前,一直采用BIO编程模型,在Socket网络编程中,我们通常会使用ServerSocket.accept()方法获取一个新连接,该方法会阻塞当前主线程,所以通常一个连接来了后,会将其放入线程池去执行后续操作。而客户端发送请求后,先咨询服务端是否有线程相应,如果没有则会一直等待或者遭到拒绝请求,如果有的话,客户端Socket的connect方法同样会阻塞当前线程等待请求结束后才继续执行。

NIO

New IO,同步非阻塞式IO,jdk1.4后引入,主要用于解决BIO大并发的问题,由于BIO会为任何连接都分配一个线程,而操作系统资源有限,如果客户端的请求过多,服务端程序可能会因为不堪重负而拒绝客户端的请求,甚至服务器可能会因此而瘫痪。

NIO基于Reactor,当socket有流可读或可写入socket时,操作系统会相应的通知引用程序进行处理,应用再将流读取到缓冲区或写入操作系统。 也就是说,这个时候,已经不是一个连接就要对应一个处理线程了,而是有效的请求,对应一个线程,当连接没有数据时,是没有工作线程来处理的。

NIO最大的不同在于当一个新连接进入时,不会直接为其分配线程,始终只有一个Selector在不断轮询注册这些新连接,当连接准备好后,再将其加入线程池。

HTTP/1.1出现后,有了Http长连接,这样除了超时和指明特定关闭的http header外,这个链接是一直打开的状态的,这样在NIO处理中可以进一步的进化,在后端资源中可以实现资源池或者队列,当请求来的话,开启的线程把请求和请求数据传送给后端资源池或者队列里面就返回,并且在全局的地方保持住这个现场(哪个连接的哪个请求等),这样前面的线程还是可以去接受其他的请求,而后端的应用的处理只需要执行队列里面的就可以了,这样请求处理和后端应用是异步的.当后端处理完,到全局地方得到现场,产生响应,这个就实现了异步处理。

对应BIO中的ServerSocketSocket,再NIO中的编程类为:
ServerSocketChannel, SocketChannel,值得注意的是,它们的accept()connect方法均不是阻塞的,当没有连接或者连接没有立即建立时,它们都会直接返回,不会阻塞当前线程!

值得注意的是,虽然jdk已经为我们提供了NIO编程模型,但是使用难度较大,并且存在空轮询的bug。所以一般我们会考虑使用在 jdk NIO的基础上继续封装的Netty

AIO

NIO.2,异步非阻塞IO。jdk1.7引入。与NIO不同,当进行读写操作时,只须直接调用API的read或write方法即可。这两种方法均为异步的,对于读操作而言,当有流可读取时,操作系统会将可读的流传入read方法的缓冲区,并通知应用程序;对于写操作而言,当操作系统将write方法传递的流写入完毕时,操作系统主动通知应用程序。 即可以理解为,read/write方法都是异步的,完成后会主动调用回调函数。 主要在java.nio.channels包下增加了下面四个异步通道:
AsynchronousSocketChannel, AsynchronousServerSocketChannel, AsynchronousFileChannel, AsynchronousDatagramChannel
其中的read/write方法,会返回一个带回调函数的对象,当执行完读取/写入操作后,直接调用回调函数。

关注我,这里只有干货!