讲解IO思路:

BIO(一个连接一个线程)

-->大并发问题-->NIO(操作系统层面:IO多路复用)

-->NIO两个问题:1.谁去监听就绪(Boss),2.谁来处理已就绪(Work)

-->AIO:NIO的两个问题都不存在:1.每一个IO操作注册回调函数(异步),不需要多路复用器去监听就绪事件(监听完成事件?),2.无需处理IO操作,操作系统完成

简而言之,NIO的多路复用器,是通知你IO就绪事件,AIO的回调是通知你IO完成事件。AIO做的更加彻底一些。

反应器Reactor

主动器Proactor

Reactor模式下的IO操作,是在应用进程中执行的,Proactor中的IO操作是由操作系统来做的

主动和被动

以主动写为例:
Reactor将handle放到select(),等待可写就绪,然后调用write()写入数据;写完处理后续逻辑;
Proactor调用aoi_write后立刻返回,由内核负责写操作,写完后调用相应的回调函数处理后续逻辑;

可以看出,Reactor被动的等待指示事件的到来并做出反应;它有一个等待的过程,做什么都要先放入到监听事件集合中等待handler可用时再进行操作;
Proactor直接调用异步读写操作,调用完后立刻返回;

Reactor模式 
Reactor模式的处理:服务器端启动一条单线程,用于轮询IO操作是否就绪,当有就绪的才进行相应的读写操作,这样的话就减少了服务器产生大量的线程,解决了BIO的问题。(目前JAVA的NIO就采用的此种模式) 
Proactor模式 
运用于异步I/O操作,Proactor模式中,应用程序不需要进行实际的读写过程,它只需要从缓存区读取或者写入即可,操作系统会读取缓存区或者写入缓存区到真正的IO设备.

二、Java中的典型IO操作模式

2.1 同步阻塞模式

Java中的BIO风格的API,都是该模式,例如:

Socket socket = getSocket();
socket.getInputStream().read(); //读不到数据誓不返回

该模式下,最直观的感受就是如果IO设备暂时没有数据可供读取,调用API就卡住了,如果数据一直不来就一直卡住。

2.2 同步非阻塞模式

Java中的NIO风格的API,都是该模式,例如:

SocketChannel socketChannel = getSocketChannel(); //获取non-blocking状态的Channel
socketChannel.read(ByteBuffer.allocate(4)); //读不到数据就算了,立即返回0告诉你没有读到

该模式下,通常需要不断调用API,直至读取到数据,不过好在函数调用不会卡住,我想继续尝试读取或者先去做点其他事情再来读取都可以。

2.3 异步非阻塞模式

Java中的AIO风格的API,都是该模式,例如:

AsynchronousSocketChannel asynchronousSocketChannel = getAsynchronousSocketChannel();
asynchronousSocketChannel.read(ByteBuffer.allocate(4), null, new CompletionHandler<Integer, Object>() {
    @Override
    public void completed(Integer result, Object attachment) {
        //读不到数据不会触发该回调来烦你,只有确实读取到数据,且把数据已经存在ByteBuffer中了,API才会通过此回调接口主动通知您
    }
    @Override
    public void failed(Throwable exc, Object attachment) {
    }
});

该模式服务最到位,除了会让编程变的相对复杂以外,几乎无可挑剔。

三、分离快与慢

3.1 BIO的局限

一个连接一个线程,无法处理大并发问题

3.2 NIO的突破

3.2.1 突破思路

由于NIO的非阻塞特性,决定了IO未就绪时,线程可以不必挂起,继续处理其他事情。这就为分离快与慢提供了可能,高速的CPU和内存可以不必苦等IO交互,一个线程也不必局限于只为一个IO连接服务。这样,就让用少量的线程处理海量IO连接成为了可能。

3.2.2 思路落地

虽然我们看到了曙光,但是要将这个思路落地还需解决掉一些实际的问题。

a)当IO未就绪时,线程就释放出来,转而为其他连接服务,那谁去监控这个被抛弃IO的就绪事件呢?(BOSS线程进行监控)

b)IO就绪了,谁又去负责将这个IO分配给合适的线程继续处理呢?(Work线程处理IO)

为了解决第一个问题,操作系统提供了IO多路复用器(比如Linux下的select、poll和epoll),Java对这些多路复用器进行了封装(一般选用性能最好的epoll),也提供了相应的IO多路复用API。NIO的多路复用API典型编程模式如下:

// 开启一个ServerSocketChannel,在8080端口上监听
ServerSocketChannel server = ServerSocketChannel.open();
server.bind(new InetSocketAddress("0.0.0.0", 8080));
// 创建一个多路复用器
Selector selector = Selector.open();
// 将ServerSocketChannel注册到多路复用器上,并声明关注其ACCEPT就绪事件
server.register(selector, SelectionKey.OP_ACCEPT);
while (selector.select() != 0) {
    // 遍历所有就绪的Channel关联的SelectionKey
    Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
    while (iterator.hasNext()) {
        SelectionKey key = iterator.next();
        // 如果这个Channel是READ就绪
        if (key.isReadable()) {
            // 读取该Channel
            ((SocketChannel) key.channel()).read(ByteBuffer.allocate(10));
        }
        if (key.isWritable()) {
            //... ...
        }
        // 如果这个Channel是ACCEPT就绪
        if (key.isAcceptable()) {
            // 接收新的客户端连接
            SocketChannel accept = ((ServerSocketChannel) key.channel()).accept();
            // 将新的Channel注册到多路复用器上,并声明关注其READ/WRITE就绪事件
            accept.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
        }
        // 删除已经处理过的SelectionKey
        iterator.remove();
    }
}

IO多路复用API可以实现用一个线程,去监控所有IO连接的IO就绪事件。

第二个问题在上面的代码中其实也得到了“解决”,但是上面的代码是使用监控IO就绪事件的线程来完成IO的具体操作,如果IO操作耗时较大(比如读操作就绪后,有大量数据需要读取),那么会导致监控线程长时间为某个具体的IO服务,从而导致整个系统长时间无法感知其他IO的就绪事件并分派IO处理任务。所以生产环境中,一般使用一个Boss线程专门用于监控IO就绪事件,一个Work线程池负责具体的IO读写处理。Boss线程检测到新的IO就绪事件后,根据事件类型,完成IO操作任务的分配,并将具体的操作交由Work线程处理。这其实就是Reactor模式的核心思想。

3.2.3 Reactor模式

如上所述,Reactor模式的核心理念在于:

a)依赖于非阻塞IO。

b)使用多路复用器监管海量IO的就绪事件。

c)使用Boss线程和Work线程池分离IO事件的监测与IO事件的处理。

Reactor模式中有如下三类角色:

a)Acceptor。用户处理客户端连接请求。Acceptor角色映射到Java代码中,即为SocketServerChannel。

b)Reactor。用于分派IO就绪事件的处理任务。Reactor角色映射到Java代码中,即为使用多路复用器的Boss线程。

c)Handler。用于处理具体的IO就绪事件。(比如读取并处理数据等)。Handler角色映射到Java代码中,即为Worker线程池中的每个线程。

Acceptor的连接就绪事件,也是交由Reactor监管的,有些地方为了分离连接的建立和对连接的处理,为将Reactor分离为一个主Reactor,专门用户监管连接相关事件(即SelectionKey.OP_ACCEPT),一个从Reactor,专门用户监管连接上的数据相关事件(即SelectionKey.OP_READ 和SelectionKey.OP_WRITE)。

关于Reactor的模型图,网上一搜一大把,我就不献丑了。相信理解了它的核心思想,图自然在心中。关于Reactor模式的应用,可以参见著名NIO编程框架Netty,其实有了Netty之后,一般都直接使用Netty框架进行服务端NIO编程。

3.3 AIO的更进一步

3.3.1 AIO得天独厚的优势

你很容易发现,如果使用AIO,NIO突破时所面临的落地问题天然就不存在了(2个方面阐述AIO与NIO区别)。因为(1)每一个IO操作都可以注册回调函数,天然就不需要专门有一个多路复用器去监听IO就绪事件,也不需要一个Boss线程去分配事件,所有IO操作只要一完成,就天然会通过回调进入自己的下一步处理。

而且,(2)通过AIO,连NIO中Work线程去读写数据的操作都可以省略了,因为AIO是保证数据真正读取/写入完成后,才触发回调函数,用户都不必关注IO操作本身,只需关注拿到IO中的数据后,应该进行的业务逻辑。

简而言之,NIO的多路复用器,是通知你IO就绪事件,AIO的回调是通知你IO完成事件。AIO做的更加彻底一些。这样在某些平台上也会带来性能上的提升,因为AIO的IO读写操作可以交由操作系统内核完成,充分发挥内核潜能,减少了IO系统调用时用户态与内核态间的上下文转换,效率更高。

(不过遗憾的是,Linux内核的AIO实现有很多问题(不在本文讨论范畴),性能在某些场景下还不如NIO,连Linux上的Java都是用epoll来模拟AIO,所以Linux上使用Java的AIO API,只是能体验到异步IO的编程风格,但并不会比NIO高效。综上,Linux平台上的Java服务端编程,目前主流依然采用NIO模型。)

使用AIO API典型编程模式如下:

//创建一个Group,类似于一个线程池,用于处理IO完成事件
AsynchronousChannelGroup group = AsynchronousChannelGroup.withCachedThreadPool(Executors.newCachedThreadPool(), 32);
//开启一个AsynchronousServerSocketChannel,在8080端口上监听
AsynchronousServerSocketChannel server = AsynchronousServerSocketChannel.open(group);
server.bind(new InetSocketAddress("0.0.0.0", 8080));
//接收到新连接
server.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
    //新连接就绪事件的处理函数
    @Override
    public void completed(AsynchronousSocketChannel result, Object attachment) {
        result.read(ByteBuffer.allocate(4), attachment, new CompletionHandler<Integer, Object>() {
            //读取完成事件的处理函数
            @Override
            public void completed(Integer result, Object attachment) {
            }

            @Override
            public void failed(Throwable exc, Object attachment) {
            }
        });
    }
    @Override
    public void failed(Throwable exc, Object attachment) {
    }
});

3.3.2 Proactor模式

Java的AIO API其实就是Proactor模式的应用。

也Reactor模式类似,Proactor模式也可以抽象出三类角色:

a)Acceptor。用户处理客户端连接请求。Acceptor角色映射到Java代码中,即为AsynchronousServerSocketChannel。

b)Proactor。用于分派IO完成事件的处理任务。Proactor角色映射到Java代码中,即为API方法中添加回调参数。

c)Handler。用于处理具体的IO完成事件。(比如处理读取到的数据等)。Handler角色映射到Java代码中,即为AsynchronousChannelGroup 中的每个线程。

可见,Proactor与Reactor最大的区别在于

a)无需使用多路复用器。

b)Handler无需执行具体的IO操作(比如读取数据或写入数据),而是只执行IO数据的业务处理。

http://www.cnblogs.com/itZhy/p/7727569.html

【1】BIO,NIO,AIO与Reactor,Proactor的更多相关文章

  1. IO回忆录之怎样过目不忘(BIO/NIO/AIO/Netty)

    有热心的网友加我微信,时不时问我一些技术的或者学习技术的问题.有时候我回微信的时候都是半夜了.但是我很乐意解答他们的问题.因为这些年轻人都是很有上进心的,所以在我心里他们就是很优秀的,我愿意多和努力的 ...

  2. java BIO/NIO/AIO 学习

    一.了解Unix网络编程5种I/O模型 1.1.阻塞式I/O模型 阻塞I/O(blocking I/O)模型,进程调用recvfrom,其系统调用直到数据报到达且被拷贝到应用进程的缓冲区中或者发生错误 ...

  3. JAVA中的BIO,NIO,AIO

    在了解BIO,NIO,AIO之前先了解一下IO的几个概念: 1.同步 用户进程触发IO操作并等待或者轮询的去查看IO操作是否就绪, 例如自己亲自出马持银行卡到银行取钱 2.异步 用户触发IO操作以后, ...

  4. (转)也谈BIO | NIO | AIO (Java版)

    原文地址: https://my.oschina.net/bluesky0leon/blog/132361 关于BIO | NIO | AIO的讨论一直存在,有时候也很容易让人混淆,就我的理解,给出一 ...

  5. 也谈BIO | NIO | AIO (Java版--转)

    关于BIO | NIO | AIO的讨论一直存在,有时候也很容易让人混淆,就我的理解,给出一个解释: BIO | NIO | AIO,本身的描述都是在Java语言的基础上的.而描述IO,我们需要从两个 ...

  6. 【netty】(1)---BIO NIO AIO演变

    BIO NIO AIO演变 Netty是一个提供异步事件驱动的网络应用框架,用以快速开发高性能.高可靠的网络服务器和客户端程序.Netty简化了网络程序的开发,是很多框架和公司都在使用的技术. Net ...

  7. 转载:BIO | NIO | AIO

    http://my.oschina.net/bluesky0leon/blog/132361 也谈BIO | NIO | AIO (Java版)   转载自:zheng-lee博客 发布时间: 201 ...

  8. 拿搬东西来解释udp tcpip bio nio aio aio异步

     [群主]雷欧纳德简单理解 tcpip是有通信确认的面对面通信   有打招呼的过程  有建立通道的过程 有保持通道的确认    有具体传输udp是看到对面的人好像在对面等你 就往对面扔东西[群主]雷欧 ...

  9. Netty5序章之BIO NIO AIO演变

    Netty5序章之BIO NIO AIO演变 Netty是一个提供异步事件驱动的网络应用框架,用以快速开发高性能.高可靠的网络服务器和客户端程序.Netty简化了网络程序的开发,是很多框架和公司都在使 ...

随机推荐

  1. codeforces 997C.Sky Full of Stars

    题目链接:codeforces 997C.Sky Full of Stars 一道很简单(?)的推式子题 直接求显然不现实,我们考虑容斥 记\(f(i,j)\)为该方阵中至少有\(i\)行和\(j\) ...

  2. 【BZOJ3613】[HEOI2014]南园满地堆轻絮(贪心)

    [BZOJ3613][HEOI2014]南园满地堆轻絮(贪心) 题面 BZOJ 洛谷 题解 考虑二分的做法,每次二分一个答案,那么就会让所有的值尽可能的减少,那么\(O(n)\)扫一遍就好了. 考虑如 ...

  3. 不能靠眼睛之 KEIL 中失效代码灰暗特性

    @2019-02-15 [问题描述] 使用 KEIL(带灰暗特性版本) 查看头文件代码时,其中有依据不同宏定义对应不同执行语句代码部分,依据灰暗特性呈现生效与失效代码,靠眼睛反应就是灰暗呈现为失效代码 ...

  4. [NOI2016]优秀的拆分&&BZOJ2119股市的预测

    [NOI2016]优秀的拆分 https://www.lydsy.com/JudgeOnline/problem.php?id=4650 题解 如果我们能够统计出一个数组a,一个数组b,a[i]表示以 ...

  5. Codeforces Round #533 (Div. 2) C.思维dp D. 多源BFS

    题目链接:https://codeforces.com/contest/1105 C. Ayoub and Lost Array 题目大意:一个长度为n的数组,数组的元素都在[L,R]之间,并且数组全 ...

  6. LinkedList(JDK1.8)源码分析

    双向循环链表 双向循环链表和双向链表的不同在于,第一个节点的pre指向最后一个节点,最后一个节点的next指向第一个节点,也形成一个"环".而LinkedList就是基于双向循环链 ...

  7. 深入学习semaphore

    深入学习semaphore 控制同时访问资源线程数 访问特定资源前,先使用acquire(1)获得许可,如果许可数量为0,该线程则一直阻塞,直到有可用许可. 访问资源后,使用release()释放许可 ...

  8. redis 指令文档

    参考:https://redis.io/commands http://www.runoob.com/redis/redis-lists.html redis: 打开一个 cmd 窗口 使用cd命令切 ...

  9. js click 与 onclick 事件绑定,触发与解绑

    click 与 onclick 1.onclick 事件会在对象被点击时发生. <input id="btn1" type="button" onclic ...

  10. electron入门笔记(三)- 引入bootstrap

    源码:https://github.com/sueRimn/electron-bootstrap 当引入jQuery和bootstrap文件时,会报错,原因是:electron 的 Renderer ...