一、常见I/O模型
所有的系统I/O都分为两个阶段:等待就绪和操作。等待就绪动作是不使用CPU的;而真正读和写的操作(将数据从内核拷贝到用户空间)的阻塞是使用CPU的,不过这个过程很快,属于memory copy,可以理解为不耗时。
二、传统的BIO
BIO在服务器端 同步阻塞处理IO:主要是因为socket.accept()、socket.read()、socket.write()三个函数都是同步阻塞的,当一个连接在处理I/O的时候,系统是阻塞的,如果是单线程的话必然就挂死在那里;
因CPU是被释放出来的,开启多线程,就可以让CPU去处理更多的事情。
缺点:严重依赖线程
1、线程创建、销毁成本很高。
2、线程本身占据内存很大,Java线程栈目前默认分配1M的空间。
3、线程多提升线程上下文切换次数,甚至执行线程切换的时间大于线程执行的时间。
4、容易造成锯齿状的系统负载,如线程数量多而外部系统或网络不稳定,大量请求可能同时返回造成系统高负载。
三、NIO
NIO主要的事件:读就绪、写就绪、有新连接到来。
新事件到来的时候,会在selector上注册标记位,标示可读、可写或者有连接到来。
用一个死循环遍历选择在selector上标记就绪的事件,然后执行系统调用(Linux 2.6之前是select、poll,2.6之后是epoll,Windows是IOCP),还会阻塞的等待新事件的到来。
注意,事件的轮询是阻塞的(没有可干的事情必须要阻塞),即select是阻塞的,无论是通过操作系统的通知(epoll)还是不停的轮询(select,poll),这个函数是阻塞的。剩余的I/O操作都是纯CPU操作,没有必要开启多线程。
Reactor模式为同步I/O,Reactor是一个被动事件分离分发模型,将处理单元(handle)放入select(),等待事件就绪(读/写)。
Reactor实现相对简单,对于任务耗时短暂的处理场景非常高效,可解决C10K问题。
在处理单元引入固定数量线程池,有助于提高效率。
四、AIO
Proactor模式为异步I/O,AIO基于Proactor模式,在发起写操作时,由操作系统异步处理,处理完成通知分离器,分离器回调处理单元。
Proactor实现相对复杂,依赖操作系统对异步的支持,目前实现纯异步的操作系统较少,应用事件驱动的主流,还是通过select/epoll实现,Proactor处理耗时较长的并发场景表现会很好。
五、Linux I/O多路复用
Linux的select、poll、epoll 都是I/O多路复用的实现,三种实现仅仅是历史原因,相继为前个版本问题的修复和优化。