七叶笔记 » golang编程 » 聊聊Java BIO(同步阻塞IO)、NIO(非阻塞IO)、AIO(异步IO)

聊聊Java BIO(同步阻塞IO)、NIO(非阻塞IO)、AIO(异步IO)

Java 中有阻塞 IO 、非阻塞IO。阻塞IO可以理解为“一个连接对应于一 线程 ”。非阻塞IO可以理解为“一个请求(一个请求里面可能会有多个连接【长连接短连接】)对应于一线程”。

BIO

Java中BIO也成为同步阻塞IO。

同步阻塞IO模式下,服务器实现模式为一个连接对应一个线程,即:有连接请求从客户端发起时,服务器端就需要创建一个线程进行处理,如果有大量连接时,服务器就需要创建大量线程进行处理。当然可以通过 线程池 机制改善。

阻塞IO适用场景为:连接数较小且固定的架构模式,这种方式对服务器资源要求比较高,并发局限于应用中,不建议在生产环境使用。

NIO(非阻塞IO)

Java中非阻塞式IO,也叫同步非阻塞IO,其通过通道和缓冲区来读写数据。通过选择器注册和获取已准备好的感兴趣的通道事件。读数据时,有多少数据就读多少数据,读完立即返回,如 socket Channel和ServerSocketChannel、Selector等。

NIO本身是基于事件驱动思想来完成的,其主要想解决的是BIO的大并发问题。NIO基于Reactor,当socket有流可读或可写入socket时,操作系统会相应的通知引用程序进行处理,应用再将流读取到缓冲区或写入操作系统。

也就是说,这个时候,已经不是一个连接就要对应一个处理线程了,而是有效的请求,对应一个线程,当连接没有数据时,是没有工作线程来处理的。

NIO的最重要的地方是,当一个连接创建后,不需要对应一个线程,这个连接会被注册到多路复用器上面。所以,所有的连接只需要一个线程就可以搞定,当这个线程中的多路复用器进行轮询的时候,发现连接上有请求的话,才开启一个线程进行处理,也就是一个请求一个线程模式。

在NIO的处理方式中,当一个请求来的话,开启线程进行处理,可能会等待后端应用的资源(JDBC连接等),其实这个线程就被阻塞了,当并发上来的话,还是会有BIO一样的问题。

Java NIO 由以下几个核心部分组成:

  • Channels
  • Buffer s
  • Selectors

虽然Java NIO 中除此之外还有很多类和组件,但是,Channel,Buffer和Selector构成了核心的API。其它组件,如Pipe和FileLock,只不过是与三个核心组件共同使用的工具类。

Channel vs Buffer

Channel和Buffer:基本上,所有的IO在NIO中都从一个Channel开始。Channel有点像流,数据可以从Channel读到Buffer中,也可以从Buffer写到Channel中。如:Channel和Buffer有好几种类型。下面是JAVA NIO中的一些主要Channel的实现:FileChannel、DatagramChannel、SocketChannel、ServerSocketChannel。这些通道涵盖了UDP 和 TCP 网络IO,以及文件IO。

Java NIO里关键的Buffer实现: byte Buffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、 long Buffer、 short Buffer、MappedByteBuffer(内存映射文件 )。这些Buffer覆盖了能通过IO发送的基本数据类型:byte,short,int,long,float,double和char。

Channel和Buffer使用案例:改代码以随机读写方式打开nio-data.txt文件,一个字节一个字节的把文件内容读入新分配的buf缓存中。

Selector

Selector允许单线程处理多个 Channel。如果应用打开了多个 连接 (通道),但每个连接的流量都很低,使用Selector就会很方便。例如,在一个聊天服务器中。单线程中使用一个Selector处理3个Channel的图示:

要使用Selector,得向Selector注册Channel,然后调用它的select()方法。 这个方法会一直阻塞 到某个注册的通道有事件就绪。一旦这个方法返回,线程就可以处理这些事件,事件的例子有如新连接进来,数据接收等。

Selector使用案例:该案例使用Selector已订阅事件的通道进行选择,当订阅事件发生时即可另行开辟线程处理该通道事件。

AIO(异步IO)

Java中的异步IO,通过Future轮询和 Callback 回调两种方式来使用。

与NIO不同,当进行读写操作时,异步IO情况下,我们只需要直接调用API的read或write方法即可。这两个操作都是异步的。

  • 对于读操作而言,当有流可读时,操作系统会将可读的流传入read方法的缓冲区,并通知应用程序;
  • 对于写操作而言,当操作系统将write方法传递的流写入完毕时,操作系统主动通知应用程序。

即可以理解为,read/write方法都是异步的,完成后会主动调用回调函数。在JDK1.7中,这部分内容被称作NIO.2,主要在java.nio.channels包下增加了下面四个异步通道:AsynchronousSocketChannel、AsynchronousServerSocketChannel、AsynchronousFileChannel、AsynchronousDatagramChannel。

Future轮询模式

典型的Java通道类有AsynchronousFileChannel。来看一个例子:

该案例通过异步IO通道AsynchronousFileChannel将文件foobar.txt的内容读入buffer中,在通过Future获取结果。Java将读取文件的操作交给操作系统底层去做,自己只需要知道文件是否读取完毕就好了。其实底层JVM为执行这个任务创建了线程池和通道组。具体可以参考AsynchronousFileChannel的官方说明:

Callback回调模式

Future其实本质上还是轮循的方式,回调式才是真正的AIO。其基本思想是主线程注册一个 CompletionHanlder 执行IO操作。 CompletionHanlde 将带着IO操作的结果返回到主线程中,这个结果会触发它自己的completed或者failed方法(需要重写这两个方法):

  1. void completed(V result, A attachment) – executes if a task completes with a result of type V.
  2. void failed(Throwable e, A attachment) – executes if the task fails to complete due to Throwable e.

callback回调案例:

上面的例子是基于文件的AsynchronousFileChannel,但是基于网络套接字的AsynchronousServerSocketChannel和AsynchronousSocketChannel也是一样的模式。

相关文章