Deep Dive 3 - NIO

Posted erixhao

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Deep Dive 3 - NIO相关的知识,希望对你有一定的参考价值。


我们来继续80后Deep Dive 3 - NIO。


Java NIO(New IO)是Java 1.4中引入的,时间的话已经是2002年了,确实久远。NIO的全称是New IO,作者偷懒就直接称之为NIO, 反而听起来酷酷的。


1. NIO 之父


老样子,我们先看看NIO的作者,NIO之父Mark Reinhold。


Mark大叔毕业于MIT Ph.D.,老SUN员工了。大叔1996年加入Sun,一待就是13年,全心开发Java,后来Oracle于2010年收购SUN,被迫变成Oracle员工,担任Java首席架构师一直到现在,说白了一直从事Java开发19年了,不对是“一直开发Java19年”, 区别大了。

大牛有一段自我介绍中提及了Sun,很有意思, 只可意会。

Prior to working at Oracle I did much the same thing at Sun Microsystems, a great company which was too lucky for its own good during the boom years and subsequently driven into the ground by a false prophet who was long on vision and short on execution.

http://mreinhold.org/

不小心故意瞅了一眼大师的朋友圈,好家伙,果然大师的朋友圈都是大神。


看到第一个了么?Java之父啊。没个XXO头衔都不好意思加好友。


2. NIO 概述



可以看出NIO核心主要提供了Channel与Buffer,以及隐含在包中的异步IO与Selector。


  • Channel与Buffer : 标准的IO是基于字节流与字符流操作的, 而NIO是基于Channel通道与Buffer缓冲区的。
  • Non-Blocking IO  : NIO可以做到非阻塞IO(除文件IO),如线程可以Channel读取数据到Buffer, 同时还可以并行做其他事情;当数据写入缓冲区后可以继续;
  • Selector :选择器Selector可以用来监听多个事件,如单个线程监听多个数据通道等。

2.1 Channel


Channel接口定义很清爽,少到几乎不看注视不知道干嘛。好吧,注释有云:Channel代表了与一个实体对象entity的连接,如文件,网络socket, 甚至硬件设备等。

大体来说,Channel的目的是为其子类及实现支持并发多线程访问的安全环境。Channel有点类似我们熟悉的流,数据可以与Buffer进行双向交互。

如图:(图来自jenkov.com/)


主要实现有以下类涵盖了文件,TCP, UDP.

  • FileChannel
  • DatagramChannel
  • SocketChannel
  • ServerSocketChannel


2.2 Buffer


Buffer简单来说是一块可被读写的内存数据块,封装成Buffer对象用来交互。


Buffer根据数据类型又分成如上几种。Buffer有几个核心属性我们需要了解一下。

上图:


首先,Buffer区分读模式与写模式。


  • Capacity 即其Buffer的总容量;单位为当前数据类型,如1024 capacity long数据。可以在其满了以后,清空(读数据/清除)。
  • Position 请看图即可;No? 写模式就是当前写的位置,初始为0, 并随着数据的写入而动态变化;通过flip切换到读模式时,首先重置0,当读取数据时,position会动态移动到下一个可读位置。
  • Limit    写模式下表示最多能写多少数据,等于capacity;切换到读模式后,limit会设置成为写模式的position位置,即可以读到之前写入的所有数据;
  • flip()    将Buffer从写模式切换到读模式,即会将position设为0, 并将limit设置为之前的position值。

Buffer 可以通过allocate方法来分配, 如:

  1. CharBuffer buf = CharBuffer.allocate(1024);  

看一个简单例子:


稍微提及一下java的基本IO操作模型,以文件copy为例:

  • 开启输入流将文件读入内存:输入数据先进入Kernel区域,再copy到JVM

  • 开启输出流将内存中的数据输出到另一个文件: 先由JVM cop到Kernel再到终端

ByteBuffer

其中ByteBuffer又分为HeapByteBufferDirectByteBuffer.



显然,一个是在java堆中分配空间,一个是在c heap中分配空间,即非gc托管,无法进行垃圾回收。所以,c heap的buffer需要手工释放。

所以,按照我们上文提及的基本IO操作模型,direct buffer显然省掉了jvm copy这个过程,速度会加快。

另外,稍微提及一下,Off-Heap Buffer经常用来做大对象缓存, 为什么? 因为这样可以脱离GC管理,不被垃圾回收掉。


2.2 Non-Blocking iO

Blocking v.s. Non-Blocking:  阻塞IO是在调用某方法时线程是处于阻塞的,它会一直等待数据返回或者超时才返回,如InputStream.read();  ServerSocket.accept(); 说的再细点的话,结合操作模型,程序首先发送请求给内核kernel,然后内核去进行网路通信,如果是read(), 在内核准备好数据前,这个线程一直会被挂起,一直等待。

详细过程如下:


有了上面的铺垫,则对应的非阻塞则好理解了:


一个明显区别就是,当发起第一次call请求后,Non-Blocking的线程并没有被阻塞,但它也没有去做别的事,而是不断的发起call请求去检测等待返回。这样的机制,其实只用一个线程来监听,其它线程都可以去做其他事情, 从而引出了NIO中的selector机制。


Selector选择机制

Selector(选择器)是Java NIO中能够检测一到多个NIO通道,并能够知晓通道是否为诸如读写事件做好准备的组件。这样,一个单独的线程可以管理多个channel,从而管理多个网络连接。 


双向通道channel上我们可以注册我们感兴趣的事件:


事件名 对应值
服务端接收客户端连接事件 SelectionKey.OP_ACCEPT(16)
客户端连接服务端事件 SelectionKey.OP_CONNECT(8)
读事件 SelectionKey.OP_READ(1)
写事件 SelectionKey.OP_WRITE(4)


代码如下: