近来看了bio,nio,aio觉得有些混杂,想写几篇博客来梳理总结一下。
本章将会分为四个部分:
- 概论
- 同步阻塞bio
- 同步非阻塞nio及reactor模型
- 异步非阻塞aio及proactor模型
是否阻塞和同步异步都是程序实现方式,这里只讲述这些实现方式在网络IO方面的作用及影响。
###是否阻塞&同异步
在学习IO体系的过程中接触到最多的四个名词
- 阻塞
- 非阻塞
- 同步
- 异步
名词 | 解释 | 举例 |
---|---|---|
阻塞 | 当试图对IO流进行读写时, 如果当时没有东西可读或者暂时不可写, 程序就进入等待状态, 直到有东西可读或者可写才继续执行 | 电话接通,你竖着耳朵听,等待对方说话,对方不说你就一直等着,直到对方说了话你就开始听取他说的话。等待说话的过程你没办法做其他事就一直等着 |
非阻塞 | 非阻塞状态下, 如果没有东西可读, 或者不可写, 读写函数马上返回, 而不会等待 | 小时候等妈妈买菜回家,想在楼下帮妈妈拿东西上楼,你下楼看看你妈妈到了没,要是没到你们上楼继续看你的电视,想着过一会儿再来下看看,而不是在楼下等待 |
同步 | 用户进程触发IO操作并等待或者轮询的去查看IO操作是否就绪 | 自己上街买衣服,自己亲自干这件事,别的事干不了。 |
异步 | 异步是指用户进程触发IO操作以后便开始做自己的事情,而当IO操作已经完成的时候会得到IO完成的通知(异步的特点就是通知) | 告诉朋友自己合适衣服的尺寸,大小,颜色,让朋友委托去买,然后自己可以去干别的事,等朋友回来就会跟我们说买的结果(使用异步IO时,Java将IO读写委托给OS处理,需要将数据缓冲区地址和大小传给OS) |
一、阻塞和非阻塞
是针对于进程在访问数据的时候,根据IO操作的就绪状态来采取的不同方式,说白了是一种读取或者写入操作函数的实现方式,阻塞方式下读取或者写入函数将一直等待,而非阻塞方式下,读取或者写入函数会立即返回一个状态值。
二、 同步和异步
- 是针对应用程序和主线程的交互而言的;
- 如果主线程需要等待执行过程,直到结束才能继续执行下面的代码,那么无疑就是同步;
- 而异步多与回调机制一起使用,为什么呢?在主线程中我们使用异步调用之后就什么都不理继续执行下面的程序。那么异步调用的程序执行的情况结果我们不就不得而知,这个时候就使用回调调用主线程的“通知程序”让主线程获知是否成功改做什么处理的等等;
- 异步实现有两种定义,第一是某个工作交给另外一个线程去执行,自己不需要涉及处理过程,直到异步线程处理结束之后回调。二是在异步IO通信中,IO操作是调用了系统的api帮你主动处理连接和读写数据,然后回调直接传输回应用层,不用自己调用函数去获取,执行完成后返回结果。
不熟悉回调的可以看一下:http://www.jianshu.com/p/67190bdce647
下面我们再来理解组合方式的IO类型,就好理解多了。
-
同步阻塞IO(java BIO):
最入门的网络编程中,我们在服务器端使用ServerSocket的accept()方法等待连接,或者使用read()和write()方法进行数据读写,当没有请求连接或者没有数据进行读写时,程序会“卡在”那行代码处,直到有请求或者数据代码才继续执行。这就是阻塞,而同步为读写过程中程序都是在所在主线程执行,我们需要等待执行结束之后才能进行其他的代码操作。 -
同步非阻塞IO(java NIO):
java的nio即为同步非阻塞IO,客户端发送的连接和读写请求都会注册到多路复用器上,注意,这里就不会在某一个连接或者读写请求上阻塞住,注册完之后我们可做自己的事,需要的话再通过多路复用器select()到连接请求或I/O请求时才进行处理,而这个select()操作占用着我们的主线程,所以称为同步。 -
异步阻塞IO:
异步就是不阻塞的,所以这个异步阻塞概念有点矛盾。阻塞与非阻塞都可以理解为同步范畴下才有的概念,对于异步,我们直接发起一个IO操作调用了操作系统api,接着就可以执行我们接下来的程序,异步IO操作结束后就会通知主线程。所以异步下对于我们的主线程来说是肯定不会阻塞的。 -
异步非阻塞IO(Java AIO(NIO.2)):
在此种模式下,用户进程只需要发起一个IO操作然后什么都不用理继续执行我们接下来的程序,等IO操作真正的完成以后,应用程序会通过回调调用我们实现的接口进行处理,而真正的IO操作已经委托JVM或者操作系统完成了。
同步和异步是针对应用程序和内核的交互而言的,同步指的是用户进程触发IO操作并等待执行过程,而异步是指用户进程触发IO操作以后便开始做自己的事情,而当IO操作已经完成的时候会得到IO完成的通知。而阻塞和非阻塞是针对于进程在访问数据的时候,根据IO操作的就绪状态来采取的不同方式,说白了是一种读取或者写入操作函数的实现方式,阻塞方式下如果数据未准备完毕,读取或者写入函数将一直等待,而非阻塞方式下,读取或者写入函数会立即返回一个状态值。
我觉得我要讲通俗一点,你要“在冰箱拿一个雪糕喂猫”:
- 你在冰箱找雪糕,找不到就一直等直到冰箱有雪糕,你就拿了喂猫。这个事情是你在做,你找不到冰淇淋你要等知道有,这是同步阻塞。
- 你在冰箱找雪糕,找不到去做其他事,等下再来找,找到了就拿了喂猫。这个事情是你在做,找不到雪糕就走等下再来,这是同步非阻塞。
- 你跟你儿子(操作系统)说,看到那个冰箱没,看到那只猫没,去冰箱拿雪糕喂猫。然后你就去做其他事了,你儿子做好之后再来跟你说做得是否成功。那么儿子究竟是怎么做的,是一直等着冰箱有雪糕再去拿还是隔一会儿看一次,还是直接叫毛自己吃,这个跟我们没关系(底层实现),让傻逼儿子去做就好。我们跟儿子说一声就去做自己的事,到时儿子会自己来告诉我们,很明显没有我们不会阻塞,事情也不是我们做的,故为异步非阻塞。
- 而为什么没有异步阻塞呢?这个是对应去冰箱拿雪糕直到有雪糕才结束,然后接着叫儿子去喂猫。
我们如果把“在冰箱拿雪糕喂猫”作为一件不可拆分的事,那么很明显,异步是肯定不会阻塞的,因为我们直接叫儿子去冰箱拿雪糕喂猫就去做自己的事了,他是不是阻塞地去拿雪糕不关我们事,我们命令一下就走了。
那如果把拿冰淇淋和喂猫分出来,就像上面说的我们去冰箱拿雪糕直到有雪糕才走,然后把雪糕给儿子叫他去喂猫。这个确实看起来跟异步阻塞一样,可是java并没有实现啊哈哈哈。而且也根本没必要实现,异步非阻塞aio已经作为一个整体动作帮我们完成了这个操作。就算你自己写了多线程,自己写一个儿子线程去异步执行喂猫操作,那也不叫做异步。前面说了异步有两种,这里的是IO异步,我们自己程序即使你用了多线程处理进行喂猫操作也是“伪异步”,网络IO异步指的是你直接叫操作系统帮你做拿雪糕和喂猫这件事。
BIO、NIO、AIO适用场景分析:
BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。
NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。
AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。