Java IO之路

学习Netty之前,先回顾一下Java的IO,从BIO到NIO,再到1.7版本的AIO。IO是由操作系统支持的API,比如在Linux环境下使用的BIO是通过recvfrom实现的,下面回顾一下相关的知识。

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

在回顾IO之前,先来复习一下这些概念,区分同步、异步、阻塞和非阻塞能够更好地理解不同种类的IO。首先来看看什么是同步和异步,同步与异步最根本的区别。举个例子,男人与女人一起吃饭,男人负责煮饭,女人负责点菜,然后女人点完菜之后,跟着男人进了厨房,等男人将菜做好后,女人把菜拿到饭桌上;又或者说女人在饭桌上做着自己的事情,但是每隔一小段时间就问男人:菜做好了没,这种情况就属于同步。如果说,女人就坐在饭桌上看着男人在厨房忙碌什么都不干或者低头玩手机,等待男人做好菜后,男人告诉女人:菜做好了,并把菜端过来,这种就属于异步。

由此可见,同步与异步的最根本的区别就是,同步需要事件发起者去询问结果,而异步则是由事件处理者在完成事件后通知发起者。根据上述的例子又可知,女人什么都不干的情况就属于阻塞的情景,而女人做自己的事情的情景就属于非阻塞。上述例子描述了同步阻塞、同步非阻塞、异步阻塞、异步非阻塞,现在对这些就应该很清楚了:

  • 同步阻塞:发起人将事情交给处理人处理时,发起人什么都不干,直到发起人发现处理人已经把事情做完后,再去做自己想做的事情。
  • 同步非阻塞:发起人将事情交给处理人处理时,发起人可以做自己的事情,但是会不断地询问处理人是否将事情处理完成。
  • 异步阻塞:发起人将事情交给处理人处理时,发起人什么都不做,干等着直到处理人通知发起人事情已经处理完成。(这是一种很傻的行为)
  • 异步非阻塞:发起人将事情交给处理人处理时,发起人去做其他事情,处理人通知发起人事情处理完成。(高性能)

BIO、NIO和AIO

在回顾了同步、异步、阻塞和非阻塞之后,我们来看看IO的分类,实际上IO是有五种的,分别为:同步阻塞IO、同步非阻塞IO、多路复用IO、信号驱动IO和异步IO。

阻塞IO模型:最早期的IO模型,实现起来也是非常简单的,只需要绑定端口、监听端口即可启动一个同步阻塞的网络IO程序。BIO就是同步阻塞的IO,它是面向流的,在编写BIO程序的时候只需要绑定端口,监听,使用read()进入阻塞等待网卡接收数据、拷贝数据到应用空间。对于一些并发不高的应用来说,BIO是非常方便的,并且由于读取数据的时候处于阻塞状态,该程序就会被挂起,并不会浪费CPU资源。

Block-io
Block-io

非阻塞IO模型:同步非阻塞IO实际上是在获取数据的时候,如果还没有数据到来,则返回一个错误,这样一来程序就会不断地询问是否有数据到来,在没有数据到来的情况属于空转,是一种浪费CPU资源的行为。

Nonblock-io
Nonblock-io

多路复用IO模型:也就是多个进程的IO可以注册在一个复用器上,这个复用器叫做Selector,在没有数据到达时,select进程将会阻塞,而数据到达后,select调用进程会返回。多路复用I/O模型和阻塞I/O模型并没有太大的不同,因为使用多路复用的优势就在于阻塞的是它可以处理多个连接。比如说Java的NIO和Nginx都使用了多路复用IO模型。

Selector-io
Selector-io

信号驱动IO模型:是通过信号处理函数,在数据到达时,通知应用程序调用IO获取数据,这种情况仍然需要应用程序去获取数据,所以并不属于异步,在开发中也很少见到这种例子。

Signal-io
Signal-io

异步IO模型:通过aio_read让内核在IO操作完成后,通知应用程序。应用程序在调用完之后可以完全不管数据什么时候会来,直接去做自己的事,当数据到达,内核拷贝数据完成后,通知应用程序数据已经拷贝完成。

Aio
Aio

综上所述,通过下面这张图来看看这五种IO的比较:

IO比较
IO比较

总结

以前在同步、异步、阻塞和非阻塞上的认识不够清晰,现在看来,同步异步最主要的区别是,异步将事情做好后,以回调、消息队列等形式通知发起者事件完成,发起者执行相关的操作即可,不需要去做获取数据的事情。回调方法是最能够体现异步和同步的区别,前端使用ajax的时候,通常都是传递callback来对请求成功和失败进行回调,此时我们可以不管这个ajax了,ajax请求完成后,会自动执行回调函数。阻塞和非阻塞也只是说是否需要等待服务方执行完毕,发起方才能执行其他操作,需要等待则是阻塞,不需要则是非阻塞。