BIO、NIO、AIO的区别


        IO模型就是说用什么样的通道进行数据的发送和接收,首先要明确一点:IO是操作系统与其他网络进行数据交互,JDK底层并没有实现IO,而是对操作系统内核函数做的一个封装,IO代码进入底层其实都是native形式的。Java共支持3种网络编程IO模式:BIO,NIO,AIO。下文进行介绍


1. BIO

        BIO(Blocking IO) 又称同步阻塞IO,一个客户端由一个线程来进行处理,伪代码如下:
在这里插入图片描述
可以看到服务端的线程阻塞在两个地方

  • 接受客户端请求的accept()函数
  • 读取数据的read()函数

其中读取数据的read()函数需要经过内核态 - 用户态的切换,read()的阻塞主要发生在

  1. 数据从网卡拷贝到内核态
  2. 数据从内核态拷贝到用户态
    在这里插入图片描述

        这就是传统的阻塞IO,如果客户端一直占用连接不发送数据,那么服务端将一直阻塞在read()函数上,无法接受其他客户端请求!为了解决这种阻塞,可以每次都创建一个新的进程或线程,去调用 read 函数,这样采用多线程的方式就不用阻塞在原客户端的 read 请求上。

        不过,这不叫非阻塞 IO,只不过用了多线程的手段使得主线程没有卡在 read 函数上不往下走罢了。操作系统为我们提供的 read 函数仍然是阻塞的。所以真正的非阻塞 IO,不能是通过我们用户层的小把戏,而是要恳请操作系统为我们提供一个非阻塞的 read 函数。
在这里插入图片描述
BIO代码示例

public class SocketServer {
    public static void main(String[] args) throws IOException {
        //创建socket连接,端口为9000
        ServerSocket serverSocket = new ServerSocket(9000);
        while (true) {
            System.out.println("等待连接。。");
            //阻塞方法
            Socket clientSocket = serverSocket.accept();
            System.out.println("有客户端连接了。。");
        <span class="token comment">//单线程连接,性能不好,下面开启多线程</span>
        <span class="token comment">//handler(clientSocket);</span>

        <span class="token comment">//开启多线程</span>
        <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Runnable</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
            <span class="token annotation punctuation">@Override</span>
            <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">run</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
                <span class="token keyword">try</span> <span class="token punctuation">{<!-- --></span>
                    <span class="token function">handler</span><span class="token punctuation">(</span>clientSocket<span class="token punctuation">)</span><span class="token punctuation">;</span>
                <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">IOException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
                    e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
                <span class="token punctuation">}</span>
            <span class="token punctuation">}</span>
        <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

<span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">handler</span><span class="token punctuation">(</span><span class="token class-name">Socket</span> clientSocket<span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token class-name">IOException</span> <span class="token punctuation">{<!-- --></span>
    <span class="token keyword">byte</span><span class="token punctuation">[</span><span class="token punctuation">]</span> bytes <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token keyword">byte</span><span class="token punctuation">[</span><span class="token number">1024</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
    <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"准备read。。"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token comment">//接收客户端的数据,阻塞方法,没有数据可读时就阻塞</span>
    <span class="token keyword">int</span> read <span class="token operator">=</span> clientSocket<span class="token punctuation">.</span><span class="token function">getInputStream</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">read</span><span class="token punctuation">(</span>bytes<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"read完毕。。"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>read <span class="token operator">!=</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
        <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"接收到客户端的数据:"</span> <span class="token operator">+</span> <span class="token keyword">new</span> <span class="token class-name">String</span><span class="token punctuation">(</span>bytes<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> read<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    clientSocket<span class="token punctuation">.</span><span class="token function">getOutputStream</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">write</span><span class="token punctuation">(</span><span class="token string">"HelloClient"</span><span class="token punctuation">.</span><span class="token function">getBytes</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    clientSocket<span class="token punctuation">.</span><span class="token function">getOutputStream</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">flush</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41

上边是BIO连接的示例代码,启动后可以通过telnet与 localhost 9000 建立连接,并发送字符串信息123,测试结果如下:
在这里插入图片描述
在这里插入图片描述
测试成功,但BIO现在已经用的不多了,因为它在大并发下有几个致命的缺点:

  1. 如果BIO使用单线程接受连接,则会阻塞其他连接,效率较低。
  2. 如果使用多线程虽然减弱了单线程带来的影响,但当有大并发进来时,会导致服务器线程太多,压力太大而崩溃。
  3. 就算使用线程池,也只能同时允许有限个数的线程进行连接,如果并发量远大于线程池设置的数量,还是与单线程无异
  4. 操作系统里read函数操作是阻塞操作,如果连接不做数据读写操作会导致线程阻塞,就是说只占用连接,不发送数据,则会浪费资源。比如线程池中500个连接,只有100个是频繁读写的连接,其他占着茅坑不拉屎,浪费资源!
  5. 另外多线程也会有线程切换带来的消耗

综上所述,BIO方式已经不适用于如下的大并发场景,仅适用于连接数目比较小且固定的架构。这种方式对服务器资源要求比较高,但BIO程序简单易理解。


2. NIO

        

2.1 操作系统的非阻塞read函数

        上边说到,用户层无论是否采用多线程,操作系统的read函数本质上还是阻塞的,需要从操作系统层面找到一个非阻塞的 read 函数。操作系统提供了这样的功能,只需要在调用 read 前,将文件描述符设置为非阻塞即可(serverSocket.configureBlocking(false);)。

        这个 read 函数的效果是,如果没有数据到达时(到达网卡并拷贝到了内核缓冲区),立刻返回一个错误值(-1),而不是阻塞地等待。这样,就需要用户线程循环调用 read,直到返回值不为 -1,再开始处理业务。流程如下:
在这里插入图片描述
注意:

  • 数据还未到达网卡,或者到达网卡但还没有拷贝到内核缓冲区之前,这个阶段是非阻塞的。
  • 当数据已到达内核缓冲区,此时调用 read 函数仍然是阻塞的,需要等待数据从内核缓冲区拷贝到用户缓冲区,才能返回。

整体流程如下图
在这里插入图片描述

2.2 NIO 多路复用

        操作系统的read函数解决了读数据时,数据在 网卡 - 内核态 的阻塞问题。但面临多个客户端连接时,如果为每个客户端创建一个线程,服务器端的线程资源还是很容易被耗光。

        面对这种问题,有个聪明的办法就是可以每 accept 一个客户端连接后,将这个文件描述符(connfd)放到一个数组里。然后弄一个新的线程去不断遍历这个数组,调用每一个元素的非阻塞 read 方法,这样,我们就成功用一个线程处理了多个客户端连接。

        但这种做法看起来像多路复用(多个连接都用一个线程处理了嘛),其实不然,这种遍历方式也只是我们用户自己想出的小把戏,每次遍历遇到 read 返回 -1 时仍然是一次浪费资源的系统调用(用户态 - 内核态)。真正的多路复用其实是 把一批文件描述符的集合通过一次系统调用传给内核,只在内核态去遍历,用户态仅处理内核态的遍历结果,这样就减少了 用户态- 内核态的请求次数,从根本上解决了这个问题

        NIO的多路复用底层主要用的是Linux 内核函数(select,poll,epoll)来实现的。windows不支持epoll实现,windows底层是基于winsock2的select函数实现的(不开源)。三种内核模型的区别如下所示!

selectpollepoll(jdk 1.5及以上)
操作方式遍历遍历回调
底层实现数组数组链表哈希表
IO效率每次调用都进行线性遍历,时间复杂度为O(n)每次调用都进行线性遍历,时间复杂度为O(n)事件通知方式,每当有IO事件就绪,系统注册的回调函数就会被调用,时间复杂度O(1)
最大连接有上限(1024个)无上限无上限

NIO 有三大核心组件:

  1. Buffer(缓冲区):buffer 底层就是个数组
  2. Channel(通道):channel 类似于流,每个 channel 对应一个 buffer缓冲区
  3. Selector(多路复用器):channel 会注册到 selector 上,由 selector 根据 channel 读写事件的发生将其交由某个空闲的线程处理

注意:NIO 的 Buffer 和 channel 都是既可以读也可以写,NIO的多路复用示意图如下:
在这里插入图片描述

应用场景:

  • NIO方式适用于连接数目多且连接比较短(轻操作) 的架构, 比如聊天服务器, 弹幕系统, 服务器间通讯,编程比较复杂


2.3 select模型

        select 是操作系统提供的系统调用函数,通过它,我们可以把一个文件描述符的数组发给操作系统, 让操作系统去遍历,确定哪个文件描述符可以读写, 然后告诉我们去处理
在这里插入图片描述

        select每次都会轮询所有的sockchannel看下哪个channel有读写事件,有的话就处理,没有就继续遍历,select和poll模型作为NIO的早期实现,存在一定弊端。下面是一段代码,简单表述一下他们的弊端!

第一个版本:NIO的早期版本模拟代码实现(select)

public class NioServer {
<span class="token comment">// 保存客户端连接</span>
<span class="token keyword">static</span> <span class="token class-name">List</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">SocketChannel</span><span class="token punctuation">&gt;</span></span> channelList <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ArrayList</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token punctuation">&gt;</span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token class-name">String</span><span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token class-name">IOException</span><span class="token punctuation">,</span> <span class="token class-name">InterruptedException</span> <span class="token punctuation">{<!-- --></span>

    <span class="token comment">// 创建NIO ServerSocketChannel,与BIO的serverSocket类似</span>
    <span class="token class-name">ServerSocketChannel</span> serverSocket <span class="token operator">=</span> <span class="token class-name">ServerSocketChannel</span><span class="token punctuation">.</span><span class="token keyword">open</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    serverSocket<span class="token punctuation">.</span><span class="token function">socket</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">bind</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">InetSocketAddress</span><span class="token punctuation">(</span><span class="token number">9000</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    
    <span class="token comment">// 设置ServerSocketChannel为非阻塞, 配置为true,则和BIO类似</span>
    serverSocket<span class="token punctuation">.</span><span class="token function">configureBlocking</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"服务启动成功"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
        <span class="token comment">// 非阻塞模式accept方法不会阻塞,否则会阻塞</span>
        <span class="token comment">// NIO的非阻塞是由操作系统内部实现的,底层调用了linux内核的accept函数</span>
        <span class="token class-name">SocketChannel</span> socketChannel <span class="token operator">=</span> serverSocket<span class="token punctuation">.</span><span class="token function">accept</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        
        <span class="token keyword">if</span> <span class="token punctuation">(</span>socketChannel <span class="token operator">!=</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token comment">// 如果有客户端进行连接</span>
            <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"连接成功"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token comment">// 设置SocketChannel为非阻塞</span>
            socketChannel<span class="token punctuation">.</span><span class="token function">configureBlocking</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token comment">// 保存客户端连接在List中</span>
            channelList<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>socketChannel<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
        
        <span class="token comment">// 遍历连接进行数据读取</span>
        <span class="token class-name">Iterator</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">SocketChannel</span><span class="token punctuation">&gt;</span></span> iterator <span class="token operator">=</span> channelList<span class="token punctuation">.</span><span class="token function">iterator</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        
        <span class="token keyword">while</span> <span class="token punctuation">(</span>iterator<span class="token punctuation">.</span><span class="token function">hasNext</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
            <span class="token class-name">SocketChannel</span> sc <span class="token operator">=</span> iterator<span class="token punctuation">.</span><span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token class-name">ByteBuffer</span> byteBuffer <span class="token operator">=</span> <span class="token class-name">ByteBuffer</span><span class="token punctuation">.</span><span class="token function">allocate</span><span class="token punctuation">(</span><span class="token number">128</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token comment">// 非阻塞模式read方法不会阻塞,否则会阻塞</span>
            <span class="token keyword">int</span> len <span class="token operator">=</span> sc<span class="token punctuation">.</span><span class="token function">read</span><span class="token punctuation">(</span>byteBuffer<span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token comment">// 如果有数据,把数据打印出来</span>
            <span class="token keyword">if</span> <span class="token punctuation">(</span>len <span class="token operator">&gt;</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
                <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"接收到消息:"</span> <span class="token operator">+</span> <span class="token keyword">new</span> <span class="token class-name">String</span><span class="token punctuation">(</span>byteBuffer<span class="token punctuation">.</span><span class="token function">array</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>len <span class="token operator">==</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token comment">// 如果客户端断开,把socket从集合中去掉</span>
                iterator<span class="token punctuation">.</span><span class="token function">remove</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
                <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"客户端断开连接"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token punctuation">}</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48

从上述代码可以看到,NIO使用一个mian线程 + 一个数组 解决了BIO的痛点。具体解决方式如下

  1. 首先设置服务端连接的阻塞方式为false,代表非阻塞方式
  2. 非阻塞式接受客户端连接,意味着这段代码一直在轮询的跑,不会阻塞。
  3. 如果有客户端连接进来,就把这个连接放入list集合中
  4. 后续遍历list集合,使用内核非阻塞式读取数据(read函数)
  5. 读取完成再次轮询跑代码

测试如下:
在这里插入图片描述

        这种方式虽然解决了BIO的部分痛点,但并不是很完美。因为select模型的底层实现、io效率、最大连接数在面对高并发时还存在一定弊端!

  1. select 调用需要传入客户端连接的数组,需要拷贝一份到内核,高并发场景下这样的拷贝消耗的资源是惊人的。(epoll模型可优化为不复制)
  2. select 在内核层仍然是通过遍历的方式检查文件描述符的就绪状态,是个同步过程,只不过无系统调用切换上下文的开销。(epoll模型内核层可优化为异步事件通知)
  3. select 仅仅返回可读文件描述符的个数,具体哪个可读还是要用户自己遍历。(epoll模型可优化为只返回给用户就绪的文件描述符,无需用户做无效的遍历)


2.4 poll模型

        poll 也是操作系统提供的系统调用函数。它和 select 的主要区别就是,去掉了 select 只能监听 1024 个文件描述符的限制。


2.5 epoll模型

JDK1.5开始引入了epoll基于事件响应机制来优化NIO。epoll 主要就是针对select模型的三点弊端进行了改进:

  1. 内核中保存一份文件描述符集合,无需用户每次都重新传入,只需告诉内核修改的部分即可。
  2. 内核不再通过轮询的方式找到就绪的文件描述符,而是通过异步 IO 事件唤醒。基于事件响应的,类似于观察者模式!
  3. 内核仅会将有 IO 事件的文件描述符返回给用户,用户也无需遍历整个文件描述符集合。

具体,操作系统提供了这三个函数。

  • 第一步,创建一个 epoll 句柄

    	int epoll_create(int size);
    
       
       
    • 1
  • 第二步,向内核添加、修改或删除要监控的文件描述符。

    	int epoll_ctl(
    	int epfd, int op, int fd, struct epoll_event *event);
    
       
       
    • 1
    • 2
  • 第三步,类似发起了 select() 调用

    	int epoll_wait(
    	int epfd, struct epoll_event *events, int max events, int timeout);
    
       
       
    • 1
    • 2

内部原理如下:
在这里插入图片描述

NIO第二个版本:使用epoll模型后的代码示例

public class NioSelectorServer {
<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token class-name">String</span><span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token class-name">IOException</span><span class="token punctuation">,</span> <span class="token class-name">InterruptedException</span> <span class="token punctuation">{<!-- --></span>

    <span class="token comment">// 创建NIO ServerSocketChannel</span>
    <span class="token class-name">ServerSocketChannel</span> serverSocket <span class="token operator">=</span> <span class="token class-name">ServerSocketChannel</span><span class="token punctuation">.</span><span class="token keyword">open</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    serverSocket<span class="token punctuation">.</span><span class="token function">socket</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">bind</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">InetSocketAddress</span><span class="token punctuation">(</span><span class="token number">9000</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token comment">// 设置ServerSocketChannel为非阻塞</span>
    serverSocket<span class="token punctuation">.</span><span class="token function">configureBlocking</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token comment">// 打开Selector处理Channel,即创建epoll</span>
    <span class="token class-name">Selector</span> selector <span class="token operator">=</span> <span class="token class-name">Selector</span><span class="token punctuation">.</span><span class="token keyword">open</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token comment">// 把ServerSocketChannel注册到selector上,并且selector对客户端accept连接操作感兴趣</span>
    serverSocket<span class="token punctuation">.</span><span class="token function">register</span><span class="token punctuation">(</span>selector<span class="token punctuation">,</span> <span class="token class-name">SelectionKey</span><span class="token punctuation">.</span>OP_ACCEPT<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"服务启动成功"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
        <span class="token comment">// 阻塞等待需要处理的事件发生</span>
        selector<span class="token punctuation">.</span><span class="token function">select</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token comment">// 获取selector中注册的全部事件的 SelectionKey 实例</span>
        <span class="token class-name">Set</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">SelectionKey</span><span class="token punctuation">&gt;</span></span> selectionKeys <span class="token operator">=</span> selector<span class="token punctuation">.</span><span class="token function">selectedKeys</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token class-name">Iterator</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">SelectionKey</span><span class="token punctuation">&gt;</span></span> iterator <span class="token operator">=</span> selectionKeys<span class="token punctuation">.</span><span class="token function">iterator</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token comment">// 遍历SelectionKey对事件进行处理</span>
        <span class="token keyword">while</span> <span class="token punctuation">(</span>iterator<span class="token punctuation">.</span><span class="token function">hasNext</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
            <span class="token class-name">SelectionKey</span> key <span class="token operator">=</span> iterator<span class="token punctuation">.</span><span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token comment">// 如果是OP_ACCEPT事件,则进行连接获取和事件注册</span>
            <span class="token keyword">if</span> <span class="token punctuation">(</span>key<span class="token punctuation">.</span><span class="token function">isAcceptable</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
                <span class="token class-name">ServerSocketChannel</span> server <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token class-name">ServerSocketChannel</span><span class="token punctuation">)</span> key<span class="token punctuation">.</span><span class="token function">channel</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
                <span class="token class-name">SocketChannel</span> socketChannel <span class="token operator">=</span> server<span class="token punctuation">.</span><span class="token function">accept</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
                socketChannel<span class="token punctuation">.</span><span class="token function">configureBlocking</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
                <span class="token comment">// 这里只注册了读事件,如果需要给客户端发送数据可以注册写事件</span>
                socketChannel<span class="token punctuation">.</span><span class="token function">register</span><span class="token punctuation">(</span>selector<span class="token punctuation">,</span> <span class="token class-name">SelectionKey</span><span class="token punctuation">.</span>OP_READ<span class="token punctuation">)</span><span class="token punctuation">;</span>
                <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"客户端连接成功"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>key<span class="token punctuation">.</span><span class="token function">isReadable</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>  <span class="token comment">// 如果是OP_READ事件,则进行读取和打印</span>
                <span class="token class-name">SocketChannel</span> socketChannel <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token class-name">SocketChannel</span><span class="token punctuation">)</span> key<span class="token punctuation">.</span><span class="token function">channel</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
                <span class="token class-name">ByteBuffer</span> byteBuffer <span class="token operator">=</span> <span class="token class-name">ByteBuffer</span><span class="token punctuation">.</span><span class="token function">allocate</span><span class="token punctuation">(</span><span class="token number">128</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
                <span class="token keyword">int</span> len <span class="token operator">=</span> socketChannel<span class="token punctuation">.</span><span class="token function">read</span><span class="token punctuation">(</span>byteBuffer<span class="token punctuation">)</span><span class="token punctuation">;</span>
                <span class="token comment">// 如果有数据,把数据打印出来</span>
                <span class="token keyword">if</span> <span class="token punctuation">(</span>len <span class="token operator">&gt;</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
                    <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"接收到消息:"</span> <span class="token operator">+</span> <span class="token keyword">new</span> <span class="token class-name">String</span><span class="token punctuation">(</span>byteBuffer<span class="token punctuation">.</span><span class="token function">array</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
                <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>len <span class="token operator">==</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token comment">// 如果客户端断开连接,关闭Socket</span>
                    <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"客户端断开连接"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
                    socketChannel<span class="token punctuation">.</span><span class="token function">close</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
                <span class="token punctuation">}</span>
            <span class="token punctuation">}</span>
            <span class="token comment">//从事件集合里删除本次处理的key,防止下次select重复处理</span>
            iterator<span class="token punctuation">.</span><span class="token function">remove</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52

        在使用epoll模型之后,对简单版本的NIO做了优化处理,可以理解为在第一个版本的NIO上,又增加了一个就绪事件列表集合,这个集合中存放着有事件响应的连接,然后开启一个线程去监听这个集合,有元素的话就进行处理。

总结:

  • 一切的开始,都起源于这个 read 函数是操作系统提供的,而且是阻塞的,我们叫它 阻塞 IO。为了破这个局,程序员在用户态通过多线程来防止主线程卡死。
  • 后来操作系统发现这个需求比较大,于是在操作系统层面提供了非阻塞的 read 函数,这样程序员就可以在用户态用一个线程内通过while遍历,完成多个文件描述符的读取(如果read函数未就绪直接返回-1),这就是 非阻塞 IO
  • 但多个文件描述符的读取就需要遍历,当高并发场景越来越多时,用户态遍历的文件描述符也越来越多,相当于在 while 循环里进行了越来越多的系统调用。后来操作系统又发现这个场景需求量较大,于是又在操作系统层面提供了这样的遍历文件描述符的机制,这就是 IO 多路复用
  • 多路复用快的原因在于:把遍历操作强加到内核中,使得原来的 while 循环里多次系统调用,变成了一次系统调用 + 内核层遍历这些文件描述符。
  • 多路复用有三个函数,最开始是 select,然后又发明了 poll 解决了 select 文件描述符的限制,然后又发明了 epoll 解决 select 的三个不足。


2.6 深入hotpost源码分析epoll模型

        NIO第二个版本的代码中,与简单版本最主要的代码区别在以下三行代码

// 1. 打开Selector处理Channel,即创建epoll ,创建多路复用器
Selector selector = Selector.open();

// 2. 把ServerSocketChannel链接注册到selector多路复用器上,并且selector对客户端accept连接操作感兴趣
serverSocket.register(selector, SelectionKey.OP_ACCEPT);

// 3. 阻塞等待需要处理的事件发生
selector.select();

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

这三行代码内部是优化的核心,下面一一介绍!

2.3.1. Selector selector = Selector.open();

    public static Selector open() throws IOException {
        return SelectorProvider.provider().openSelector();
    }

= SelectorProvider.provider() ===

<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token class-name">SelectorProvider</span> <span class="token function">provider</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
    <span class="token keyword">synchronized</span> <span class="token punctuation">(</span>lock<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>provider <span class="token operator">!=</span> <span class="token keyword">null</span><span class="token punctuation">)</span>
            <span class="token keyword">return</span> provider<span class="token punctuation">;</span>
        <span class="token keyword">return</span> <span class="token class-name">AccessController</span><span class="token punctuation">.</span><span class="token function">doPrivileged</span><span class="token punctuation">(</span>
            <span class="token keyword">new</span> <span class="token class-name">PrivilegedAction</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">SelectorProvider</span><span class="token punctuation">&gt;</span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
                <span class="token keyword">public</span> <span class="token class-name">SelectorProvider</span> <span class="token function">run</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
                        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">loadProviderFromProperty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
                            <span class="token keyword">return</span> provider<span class="token punctuation">;</span>
                        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">loadProviderAsService</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
                            <span class="token keyword">return</span> provider<span class="token punctuation">;</span>
                        <span class="token comment">// 我们在Windows环境下,默认创建的是WindowsSelectorProvider,如下所示</span>
                        provider <span class="token operator">=</span> <span class="token class-name"><span class="token namespace">sun<span class="token punctuation">.</span>nio<span class="token punctuation">.</span>ch<span class="token punctuation">.</span></span>DefaultSelectorProvider</span><span class="token punctuation">.</span><span class="token function">create</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
                        <span class="token keyword">return</span> provider<span class="token punctuation">;</span>
                    <span class="token punctuation">}</span>
                <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

= 默认创建的是WindowsSelectorProvider =
public static SelectorProvider create() {
return new WindowsSelectorProvider();
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

        进入open方法源码中,可以看到创建的是WindowsSelectorProvider,而项目发布一般都是在linux上,所以需要进入hotport源码中看一下Linux平台的实现。这中现象也是JVM的跨平台性的体现。进入hotpost源码发现如果是linux环境,则创建EpollSelectorProvider。也发现Linux上实现是基于Epoll模型!在这里插入图片描述
所以 SelectorProvider.provider().openSelector() 其实是EpollSelectorProvider.openSelector(),代码如下所示!

在这里插入图片描述
可以看到 EPollSelectorImpl 是具体的实现,那么在EPollSelectorImpl实例化时做了什么事情呢?
在这里插入图片描述
        在这个 EPollArray 包装类中调用了一个 epoll_create 的本地方法,创建了一个epoll对象,并返回一个非负数作为文件描述符,用于对epoll接口的所有后续调用。这个方法属于linux操作系统内核中的方法。至此Selector.open()方法解释完毕,总的来说就是创建了一个linux内核层面的Epoll对象!
在这里插入图片描述


2.3.2. serverSocket.register(selector, SelectionKey.OP_ACCEPT)

查看这个方法的源码,同样要使用LInux下的平台中的EpollSelectorProvider的register方法,核心代码如下:

在这里插入图片描述
这个方法是把当前连接注册到第一步EPollSelectorImpl实例化时生成的EPollArray的集合中,此时并没有事件驱动。直到 selector.select()方法调用linux内核函数epoll_ctl


2.3.3. selector.select()

这个select方法内部会调用doselect()方法,这个方法同样要使用LInux下的平台中的EpollSelectorProvider
在这里插入图片描述
在这个poll方法中,分别调用了Linux核心函数 epoll_ctlepoll_wait,下面分别解释其含义

  1. epoll_ctl: 负责真正去关联socketChannel 和 epoll实例(selector)的关系。如果客户端连接(socketChannel)有发送事件,就会把存在于EPollArray中的这个连接放入epoll实例中的就绪事件列表(rdlist)中去。
    其中这个放入就绪事件列表的操作是怎么做的呢?
    放入操作并非是java去做,每当服务端收到客户端的连接,且该链接有响应事件时,就是使用操作系统的硬中断功能把该连接放入就绪事件列表中去。后续遍历就绪事件列表中的元素即可,减少了无效遍历。
  2. epoll_wait:监听就绪事件列表中的元素,如果没有数据则阻塞。如果有数据,则把数据放入selectedKeys中,供java代码获取元素,响应事件!

在这里插入图片描述

2.7 NIO与redis

        Redis就是典型的基于epoll的NIO线程模型(nginx也是),epoll实例收集所有事件(连接与读写事件),把有数据交互的连接放入就绪事件列表中,由一个服务端线程连续处理所有就绪事件列表中的命令。
在这里插入图片描述
在ae_epoll.c源码中也发现有 epoll_createepoll_ctlepoll_wait,原理与上文讲的类似

问题一:为什么 redis 不建议用 bigkey?

        bigkey的big体现在单个value值很大,一般认为超过10KB就是bigkey。由于redis底层用的是NIO,多路复用一个线程,如果存在bigkey的话,这个bigkey就会占用这个线程较大的时间,导致其他连接的数据交互阻塞,所以不建议使用bigkey。注意:这里说的阻塞并不是 异步非阻塞的阻塞。


3. AIO

        AIO自JDK1.7以后才开始支持,是异步非阻塞的,客户端与服务端的连接(accept)、数据读写(read、write)不再由main线程去执行,而是开辟一个回调函数,当客户端与服务端建立连接时,把这个客户端的连接传入回调函数中,由服务端启动一个子线程去处理,这就完成了异步操作!适用于连接数较多且连接时间较长的应用。

AIO与BIO、NIO的不同之处在于:

  1. AIO是 异步非阻塞模型
  2. NIO是 同步非阻塞模型
  3. BIO是 同步阻塞模型

AIO代码示例

public class AIOServer {
<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token class-name">String</span><span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token class-name">Exception</span> <span class="token punctuation">{<!-- --></span>
	<span class="token comment">//创建服务端</span>
    <span class="token keyword">final</span> <span class="token class-name">AsynchronousServerSocketChannel</span> serverChannel <span class="token operator">=</span>
            <span class="token class-name">AsynchronousServerSocketChannel</span><span class="token punctuation">.</span><span class="token keyword">open</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">bind</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">InetSocketAddress</span><span class="token punctuation">(</span><span class="token number">9000</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

	<span class="token comment">//使用CompletionHandler异步处理客户端连接</span>
    serverChannel<span class="token punctuation">.</span><span class="token function">accept</span><span class="token punctuation">(</span><span class="token keyword">null</span><span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token class-name">CompletionHandler</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">AsynchronousSocketChannel</span><span class="token punctuation">,</span> <span class="token class-name">Object</span><span class="token punctuation">&gt;</span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
        <span class="token annotation punctuation">@Override</span>
        <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">completed</span><span class="token punctuation">(</span><span class="token class-name">AsynchronousSocketChannel</span> socketChannel<span class="token punctuation">,</span> <span class="token class-name">Object</span> attachment<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
            <span class="token keyword">try</span> <span class="token punctuation">{<!-- --></span>
                <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"2--"</span><span class="token operator">+</span><span class="token class-name">Thread</span><span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
                
                <span class="token comment">// 在此接收客户端连接,如果不写这行代码后面的客户端连接连不上服务端</span>
                serverChannel<span class="token punctuation">.</span><span class="token function">accept</span><span class="token punctuation">(</span>attachment<span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
                <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>socketChannel<span class="token punctuation">.</span><span class="token function">getRemoteAddress</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
                <span class="token class-name">ByteBuffer</span> buffer <span class="token operator">=</span> <span class="token class-name">ByteBuffer</span><span class="token punctuation">.</span><span class="token function">allocate</span><span class="token punctuation">(</span><span class="token number">1024</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
                
                <span class="token comment">//使用CompletionHandler异步读取数据</span>
                socketChannel<span class="token punctuation">.</span><span class="token function">read</span><span class="token punctuation">(</span>buffer<span class="token punctuation">,</span> buffer<span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token class-name">CompletionHandler</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">Integer</span><span class="token punctuation">,</span> <span class="token class-name">ByteBuffer</span><span class="token punctuation">&gt;</span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
                    <span class="token annotation punctuation">@Override</span>
                    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">completed</span><span class="token punctuation">(</span><span class="token class-name">Integer</span> result<span class="token punctuation">,</span> <span class="token class-name">ByteBuffer</span> buffer<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
                        <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"3--"</span><span class="token operator">+</span><span class="token class-name">Thread</span><span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
                        buffer<span class="token punctuation">.</span><span class="token function">flip</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
                        <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">String</span><span class="token punctuation">(</span>buffer<span class="token punctuation">.</span><span class="token function">array</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> result<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
                        socketChannel<span class="token punctuation">.</span><span class="token function">write</span><span class="token punctuation">(</span><span class="token class-name">ByteBuffer</span><span class="token punctuation">.</span><span class="token function">wrap</span><span class="token punctuation">(</span><span class="token string">"HelloClient"</span><span class="token punctuation">.</span><span class="token function">getBytes</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
                    <span class="token punctuation">}</span>

                    <span class="token annotation punctuation">@Override</span>
                    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">failed</span><span class="token punctuation">(</span><span class="token class-name">Throwable</span> exc<span class="token punctuation">,</span> <span class="token class-name">ByteBuffer</span> buffer<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
                        exc<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
                    <span class="token punctuation">}</span>
                <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">IOException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
                e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token punctuation">}</span>
        <span class="token punctuation">}</span>

        <span class="token annotation punctuation">@Override</span>
        <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">failed</span><span class="token punctuation">(</span><span class="token class-name">Throwable</span> exc<span class="token punctuation">,</span> <span class="token class-name">Object</span> attachment<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
            exc<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"1--"</span><span class="token operator">+</span><span class="token class-name">Thread</span><span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token class-name">Thread</span><span class="token punctuation">.</span><span class="token function">sleep</span><span class="token punctuation">(</span><span class="token class-name">Integer</span><span class="token punctuation">.</span>MAX_VALUE<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49

AIO作为异步非阻塞模型,理论上来说应该被广泛使用,但大多数公司并没有使用AIO,而是使用了netty,为什么?

  1. 首先AIO得底层实现仍使用Epoll,并没有很好的实现异步,在性能上对比NIO没有太大优势
  2. 其次AIO的代码逻辑比较复杂,且Linux上AIO还不够成熟
  3. Netty在NIO上做了很多异步的封装,是异步非阻塞框架


4. BIO、NIO、AIO的对比

在这里插入图片描述
一个关于同步异步阻塞非阻塞的段子:

老张爱喝茶,废话不说,煮开水。
出场人物:老张,水壶两把(普通水壶,简称水壶;会响的水壶,简称响水壶)。

  1. 老张把水壶放到火上,立等水开。(同步阻塞) 老张觉得自己有点傻
  2. 老张把水壶放到火上,去客厅看电视,时不时去厨房看看水开没有。(同步非阻塞) 老张还是觉得自己有点傻,于是变高端了,买了把会响笛的那种水壶。水开之后,能大声发出嘀~~~~的噪音。
  3. 老张把响水壶放到火上,立等水开。(异步阻塞)老张觉得这样傻等意义不大
  4. 老张把响水壶放到火上,去客厅看电视,水壶响之前不再去看它了,响了再去拿壶。(异步非阻塞)老张觉得自己聪明了。

所谓同步异步,只是对于水壶而言。

  1. 普通水壶,同步
  2. 响水壶,异步。
  3. 虽然都能干活,但响水壶可以在自己完工之后,提示老张水开了。这是普通水壶所不能及的。
    同步只能让调用者去轮询自己(情况2中),造成老张效率的低下。

所谓阻塞非阻塞,仅仅对于老张而言。

  1. 立等的老张,阻塞
  2. 看电视的老张,非阻塞。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值