我们来继续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方法来分配, 如:
- CharBuffer buf = CharBuffer.allocate(1024);
看一个简单例子:
稍微提及一下java的基本IO操作模型,以文件copy为例:
- 开启输入流将文件读入内存:输入数据先进入Kernel区域,再copy到JVM
- 开启输出流将内存中的数据输出到另一个文件: 先由JVM cop到Kernel再到终端
ByteBuffer
其中ByteBuffer又分为HeapByteBuffer与DirectByteBuffer.
显然,一个是在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) |
代码如下:
值得注意的是,NIO中只支持SocketChannel与ServerSocketChannel,
FileChannel是不能实现NIO的。但我们知道上面的io与内核的交互以及DirectByteBuffer,仍然有办法加速文件的io。
2.4 AIO (Asynchronous I/O) = NIO 2.0
Java 1.7又引入了AIO, 即异步IO。细心的读者观察到,上面Non-Blocking的线程并没有被阻塞,但它也没有去做别的事,而是不断的发起call请求去检测等待返回。而可以更佳智能就好了。
想到以前看过的一本书的例子,以物流快递为例,名字忘记了。
- BlockingIO
: 你要去物流快递公司等货,并且不能离开,一直等下去。 - Non-Blocking:你只要每天去物流快递公司看一下,甚至一个小区可以派一个人去看看整个小区有没有快递,如selector模式。
- Asynchronous I/O:快递来了, 您只要签收就好。
好吧, 原来快递行业玩的也是高大上的AIO, 哈哈。
好吧,时间不早了,要上班去了。写太多看起来也不方便。抛砖引玉,希望对大家有帮助。
公众号:技术极客TechBooster
本文有些图片解释参考自:
http://tutorials.jenkov.com/java-nio/overview.html