对NIO的几点理解

点击量:145

一、基本概念
Blocking IO VS NonBlocking IO
阻塞IO:线程会一直等待/阻塞直到IO操作完成,比如准备读取10个字节的数据,但是现在只读了8个,那么当前线程会一直等下去,直到读取到剩下的2个字节的数据。这是普通IO的线程模型:
bio-png
非阻塞IO:在进行IO操作时线程并不会阻塞(等待),一次只读取能读取的数据,读完立刻返回,然后线程会去继续处理其他事情。等下次可读的时候被唤醒,再来读取数据。这是NIO的线程模型:
nio-png
因此在阻塞IO模式下,如果你要处理N个请求的话,就需要开启N个线程分别处理这些请求。因此最大的连接数取决于服务器能开出的最大线程数,虽然后期采用线程池的方式做了些优化,但总体而言性能并没有很大提升。而在非阻塞IO下只要一个线程就够了,不断的在各个连接之间切换,在某个连接有可用事件的时候通知主线程。
咋一看是不是很容易理解?但是对于NIO的工作模式我一直有个疑问:

主线程立即返回了,那读写数据的时候总得消耗线程吧?

其实真正的IO操作是内核线程,但上层也需要用户线程去调用。而且NIO所说的用户线程立刻返回并不是说不消耗线程,而是对线程的占用程度不一样,阻塞IO会一直占用线程直到IO操作完成,而NIO则只会占用注册事件可用的那段时间
sync_async

二、代码层面理解NIO
这是一个标准的基于阻塞IO的网络服务器的写法:

这是一个基于NIO的网络服务器的写法:

几乎所有的介绍NIO的文章都会贴出这两段代码来解释IO和NIO的区别,告诉你下面这段代码采用了NIO,然后就开始介绍Java NIO各种基本组件,比如channel,buffer,selector等,但是几乎所有博主都没有认真分析过这段代码是怎么体现NIO的。
代码片段fragment1和fragment2都在读数据,NIO只不过变成异步的方式去触发读写,但是读写这部分该消耗的还是要消耗啊,因为总的数据量是一样的啊。So, what’s the difference?
根本的原因是:数据的传输是一个持续的过程,但数据读写的并不一定是连续的。也就是说服务器和客户端的连接一直在,但他们之间的数据并不是一直有,而是断断续续的。普通IO的方法在没有数据的时候也会一直等,而NIO不会,只会在selector通知可读的时候才会来把buffer读满,根本木有等待的过程。理解这段代码要从最大的wihle循环入手,动态的模拟代码的执行。NIO通过不断的循环(事件通知)来读取数据,每次读取一小段,最后累加起来把所有的数据读完,而不是像阻塞IO那样一次性读完。

三、NIO与异步编程模型
此外,我们发现这段代码只有一个线程,当他去处理某个请求(读写数据)时是会阻塞的,其他请求就进不来了。这么看接收请求的能力并不比传统的IO强多少啊。注意,这段代码想说明的是NIO,与NIO相关的是IO操作的线程,与接收请求线程,处理请求线程这两个没有关系。不要以为有一个boss线程分发到请求到worker线程就是NIO了(第一段代码就是这种异步处理模式),NIO与这个线程模型没有关系,NIO的核心是selector中的事件通知。在实际使用的时候,接受请求和处理请求的线程是分开的,前者一般是一个线程,而后者会采用线程池的方式增加效率(netty就是这种方式)。而连接池中的线程会在各个channel中不停地切换(一个线程不会被一个channel占满),相比于传统的IO线程模型,在这种模式性能自然是提高了不少。

这篇日志写完理解又加深了一点(上面写的好啰嗦),其实概括下来就几点:
1.阻塞并不是IO读写,而是在IO等待(理解概念要细致具体!)
2.线程在没有数据的情况下(不可读也不可写)不会傻等,这就是NIO的核心概念。

发表评论

电子邮件地址不会被公开。 必填项已用*标注