`
huoyanxueren
  • 浏览: 38465 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

NIO学习全概括

阅读更多

前言

最近在看java NIO的一些中文资料,确实比较的头疼。文章的好坏良莠不齐,好的文章一看,受益不少;讲的不好的,被引得偏差了3百里才绕回来。更有的只有NIO 1.0的内容,2.0的只字未提。本文主要目的是对NIO知识系统进行总结和梳理,并给大家提供一篇“能看”且较全面的文章。

 

一、操作系统I/O模型

学习NIO的前提是了解操作系统的I/O模型。

 

操作系统I/O模型



  

很明显,操作系统中存在着两个空间:内核空间和用户空间。用户空间就是常规的应用进程所在的空间,一个或者多个jvm实例当然也是一个用户态的进程,所以运行需要资源都在用户空间。内核空间是操作系统内核运行所在的空间。用户空间的进行没有权限直接跨过内核空间,直接跟硬件进行I/O交互。

当应用进程请求 I/O 操作的时候,JVM底层会执行一个系统调用(也就是说JVM所完成的任何操作,最终是通过native来实现),系统调用时,会将控制权移交给内核。操作系统的底层函数 open( )、read( )、write( )和 close( )来完成最终和硬件的交互。当内核完成调用后,得到了应用进程所需数据,它会在内核空间的缓存区保留数据,并把数据传送到用户空间内的指定缓冲区。内核试图对数据进行高速缓存或预读取,因此进程所需数据可能已经在内核空间里了。如果是这样,该数据只需简单地拷贝出来即可。如果数据不在内核空间,则进程被挂起,内核着手把数据读进内存。

现代操作系统常会用到DMA(Direct Memory Access,直接内存存取)技术,MMU(Memory Management Unit,内存管理单元)。关于这些内容,大家可以自行找谷哥。一些基于I/O中的技术经常也会提到一个词“zero-copy“,相关介绍可以看我的前一篇文章 http://go-on.iteye.com/blog/1807749。


虚拟内存

虚拟内存意为物理内存(硬件RAM)大小不够用的情况,通过操作系统的磁盘等设备,通过内存交换的方式来扩充内存。虚拟内存技术的利用,使得虚拟出来的内存能够远远大于实际的物理内存。因为内存地址也是虚拟的,所以就会存在两个不同的虚拟内存地址指向同一个物理内存。
 

 

磁盘控制器不能通过DMA对用户控件的内存进行访问。但是通过虚拟内存技术,可以把用户空间的内存和内核空间的内存映射到同一个物理地址上。这样DMA可以同时把数据写入到用户空间的缓冲区和内核空间缓冲区。后面介绍的NIO “直接缓存区“的概念跟它相关。

 

发散和聚集 



 

许多操作系统能把组装/分解过程进行得更加高效。根据发散/汇聚的概念,进程只需一个系统调用,就能把一连串缓冲区地址传递给操作系统。然后,内核就可以顺序填充或排干多个缓冲区,读的时候就把数据发散到多个用户空间缓冲区,写的时候再从多个缓冲区把数据汇聚起来这样用户进程就不必多次执行系统调用(那样做可能代价不菲),内核也可以优化数据的处理过程,因为它已掌握待传输数据的全部信息。如果系统配有多个 CPU,甚至可以同时填充或排干多个缓冲区。这个知识点和NIO的特性“分散/聚集 I/O“相关。

 

二、阻塞和非阻塞、同步和异步

阻塞和非阻塞是针对于进程在访问数据的时候,根据IO操作的就绪状态采取的不同方式,说白了是一种读取或者写入操作函数的实现方式。阻塞方式下读取或者写入函数将一直等待;而非阻塞方式下,读取或者写入函数会立即返回一个状态值,继续执行后面代码。

同步和异步是针对应用程序和内核的交互而言的。同步指的是用户进程触发IO操作并等待或者轮询的去查看IO操作是否就绪;而异步是指用户进程触发IO操作以后便开始做自己的事情,而当IO操作已经完成的时候会得到IO完成的通知。


 所以I/O模型一般分为同步阻塞I/O、同步非阻塞I/O,异步阻塞I/O,异步非阻塞I/O。

同步阻塞IO:
在此种方式下,用户进程在发起一个IO操作后,用户进程hold住,等待用户空间的IO操作完成,只有当真正完成了IO操作以后,用户进程才会继续运行。JAVA传统的IO模型属于此种方式。可以参照前面的I/O模型的用户态和内核态理解。

同步非阻塞IO:
在此种方式下,用户进程发起一个IO请求以后,就可以往下继续执行,但是用户进程需要时不时地询问IO操作是否就绪。用户进程不间断的轮训,中间存在的一些进程上下文切换,会导致不必要的CPU资源浪费。目前NIO中较常用1.0的特性就属于同步非阻塞IO。
  
异步阻塞IO:
此种方式下是指用户进程发起一个IO操作以后,不等待内核IO操作的完成,等内核完成IO操作以后会通知应用程序,这其实就是同步和异步最关键的区别,同步必须等待或者主动的去询问IO是否完成,那么为什么说是阻塞的呢?因为此时是通过select系统调用来完成的,而select函数本身的实现方式是阻塞的,而采用select函数有个好处就是它可以同时监听多个文件句柄,从而提高系统的并发性。

异步非阻塞IO:
在此种模式下,用户进程只需要发起一个IO操作然后立即返回,等IO操作真正的完成以后,用户进程会得到IO操作完成的通知,此时用户进程只需要对数据进行处理就好了,不需要进行实际的IO读写操作,因为真正的IO读取或者写入操作已经由内核完成了。目前NIO 2.0的异步NIO支持此种IO模型。

关于Linux系统的select,poll,epoll系统函数介绍,见LINUX多路复用select,poll,epoll 。JDK NIO在不同操作系统下的native实现不同,但是了解了Linux系统,基本掌握了精髓。

 

三、Reactor和Proactor模式

Reactor和Proactor是IO多路复用模式.一般地,I/O多路复用机制都依赖于一个事件多路分离器(Event Demultiplexer)。分离器对象可将来自事件源的I/O事件分离出来,并分发到对应的read/write事件处理器(Event Handler)。开发人员预先注册需要处理的事件及其事件处理器(或回调函数)。

 

Reactor模式采用同步IO,而Proactor采用异步IO。

reactor

preactor

1. 应用程序注册读/写就绪事件和相关联的事件处理器

2. 事件分离器等待事件的发生 (Reactor负责)

3. 当发生读就绪事件的时候,事件分离器调用第一步注册的事件处理器(Reactor负责)

4. 事件处理器首先执行实际的读取操作,然后根据读取到的内容进行进一步的处理(用户处理器负责)

1. 应用程序初始化一个异步读取操作,然后注册相应的事件处理器,此时事件处理器不关注读取就绪事件,而是关注读取完成事件,这是区别于Reactor的关键。

2. 事件分离器等待读取操作完成事件

3. 在事件分离器等待读取操作完成的时候,操作系统调用内核线程完成读取操作(异步IO都是操作系统负责将数据读写到应用传递进来的缓冲区供应用程序操作,操作系统扮演了重要角色),并将读取的内容放入用户传递过来的缓存区中。这也是区别于Reactor的一点,Proactor中,应用程序需要传递缓存区。

4. 事件分离器捕获到读取完成事件后,激活应用程序注册的事件处理器,事件处理器直接从缓存区读取数据,而不需要进行实际的读取操作。

 

 

四、NIO入门

developerwork上有一篇文章,易懂、详细且有示例代码,看完就基本就入门NIO了,url:http://www.ibm.com/developerworks/cn/education/java/j-nio/index.html,这里就不再造轮子了。

 

五、NIO深入

关于Select机制的原理可以通过陈皓(CoolShell博主)的两篇写的蛮有意思的文章来理解。
Java NIO类库Selector机制解析(上、下)
http://haoel.blog.51cto.com/313033/124582
http://haoel.blog.51cto.com/313033/124578

刚开始学习NIO的时候,在服务器写数据的时候,看到一段关于这样的IO写代码:
1.     while (keyIterator.hasNext()) {
2.                    SelectionKey key = keyIterator.next();
3.                    keyIterator.remove();
4.                    if (key.isConnectable()) {
5.                        sc.finishConnect();
6.                        sc.register(selector, SelectionKey.OP_WRITE);
7.                        System.out.println("server connected...");
8.                        break;
9.                    } else if (key.isWritable()) {
10.     
11.                        System.out.println("please input message");
12.                        String message = scanner.nextLine();
13.                        ByteBuffer writeBuffer = ByteBuffer.wrap(message.getBytes());
14.                        sc.write(writeBuffer);
15.                    }
16.                }
其中关于SocketChannel的write()方法,为什么还要采用reactor模式进行写而大感不解,这个方法不是非阻塞的吗?只要往里面写不ok了。后来才发现自己too simple,too naive。具体为什么需要reactor模式,参照java nio 如何处理慢速的连接

 

六、NIO2 异步IO

网上大部分的关于NIO的文章,到同步非阻塞模式就嘎然而止,根本不提NIO2的异步非阻塞模式,而这样的文章被一而再,再而三的转载,却把最重要的东西湮没了,所以博文适时的更新是何其重要。关于此段内容,IBM的两位工程师提供了两篇详尽的文章,比较不错。
NIO.2 入门,第 1 部分: 异步通道 API
NIO.2 入门,第 2 部分: 文件系统 API

读完之后,你会觉得异步非阻塞模型确实很棒,这个才是我们最想要的I/O模型。

 

七、NIO开源框架

目前有较多的NIO开源框架,其中较出名的有MINA、CXF、Mule、JBoss/Geronimo,Grizzly,Cindy。有志者请自挖,这里不表。

 

八、总结

这里的总结非是以技术论断结尾,而是以学习NIO的人文关怀结尾。Java开发者一味关注的基本是类库的API,但是对于系统调用层级的理解,跟c和c++开发者,只能望其项背了。但是有JDK的新特性,无不是对于通过底层调用的优化来实现性能飞跃。这么看来,《Linux编程艺术》系列的书籍一直是被Java开发者误认的“鸡肋”罢了!

 

参考引用:

http://xmuzyq.iteye.com/blog/783218
http://developer.51cto.com/art/201112/307728.htm
http://developer.51cto.com/art/201112/307671.htm



  • 大小: 39.9 KB
  • 大小: 65.4 KB
  • 大小: 66.4 KB
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics