0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

Java AIO又称为NIO 2.0,难道它也是基于NIO来实现的?

OSC开源社区 来源:得物技术 2023-03-23 09:26 次阅读

1

前言

关于Java BIO、NIO、AIO的区别和原理,这样的文章非常的多的,但主要还是在BIO和NIO这两者之间讨论,而关于AIO这样的文章就少之又少了,很多只是介绍了一下概念和代码示例。 在了解AIO时,有注意到以下几个现象:

1、 2011年Java 7发布,里面增加了AIO称之为异步IO的编程模型,但已经过去了近12年,平时使用的开发框架中间件,还是以NIO为主,例如网络框架Netty、Mina,Web容器Tomcat、Undertow。

2、 Java AIO又称为NIO 2.0,难道它也是基于NIO来实现的?

3、 Netty舍去了AIO的支持。

4、 AIO看起来只是解决了有无,发布了个寂寞。

这几个现象不免会令很多人心存疑惑,所以决定写这篇文章时,不想简单的把AIO的概念再复述一遍,而是要透过现象, 如何分析、思考和理解Java AIO的本质。

2

什么是异步

2.1 我们所了解的异步

AIO的A是Asynchronous异步的意思,在了解AIO的原理之前,我们先理清一下“异步”到底是怎样的一个概念。 说起异步编程,在平时的开发还是比较常见,例如以下的代码示例:


@Async
public void create() {
    //TODO
}


public void build() {
    executor.execute(() -> build());
}

不管是用@Async注解,还是往线程池里提交任务,他们最终都是同一个结果,就是把要执行的任务,交给另外一个线程来执行。

这个时候,可以大致的认为,所谓的“异步”,就是多线程,执行任务。

2.2 Java BIO和NIO到底是同步还是异步?

Java BIO和NIO到底是同步还是异步,我们先按照异步这个思路,做异步编程。 2.2.1BIO示例

byte [] data = new byte[1024];
InputStream in = socket.getInputStream();
in.read(data);
// 接收到数据,异步处理
executor.execute(() -> handle(data));


public void handle(byte [] data) {
    // TODO
}
BIO在read()时,虽然线程阻塞了,但在收到数据时,可以异步启动一个线程去处理。 2.2.2NIO示例
selector.select();
Set keys = selector.selectedKeys();
Iterator iterator = keys.iterator();
while (iterator.hasNext()) {
    SelectionKey key = iterator.next();
    if (key.isReadable()) {
        SocketChannel channel = (SocketChannel) key.channel();
        ByteBuffer byteBuffer = (ByteBuffer) key.attachment();
        executor.execute(() -> {
            try {
                channel.read(byteBuffer);
                handle(byteBuffer);
            } catch (Exception e) {


            }
        });


    }
}


public static void handle(ByteBuffer buffer) {
    // TODO
}
同理,NIO虽然read()是非阻塞的,通过select()可以阻塞等待数据,在有数据可读的时候,异步启动一个线程,去读取数据和处理数据。

2.2.3产生理解的偏差

此时我们信誓旦旦的说,Java的BIO和NIO是异步还是同步,取决你的心情,你高兴给它个多线程,它就是异步的。 但果真如此么,在翻阅了大量博客文章之后,基本一致的阐明了,BIO和NIO是同步的。

那问题点出在哪呢,是什么造成了我们理解上的偏差呢? 那就是参考系的问题,以前学物理时,公交车上的乘客是运动还是静止,需要有参考系前提,如果以地面为参考,他是运动的,以公交车为参考,他是静止的。

Java IO也是一样,需要有个参考系,才能定义它是同步异步,既然我们讨论的是IO是哪一种模式,那就是要针对IO读写操作这件事来理解,而其他的启动另外一个线程去处理数据,已经是脱离IO读写的范围了,不应该把他们扯进来。

2.2.4尝试定义异步

所以以IO读写操作这事件作为参照,我们先尝试的这样定义,就是发起IO读写的线程(调用read和write的线程),和实际操作IO读写的线程,如果是同一个线程,就称之为同步,否则是异步

显然BIO只能是同步,调用in.read()当前线程阻塞,有数据返回的时候,接收到数据的还是原来的线程。

而NIO也称之为同步,原因也是如此,调用channel.read()时,线程虽然不会阻塞,但读到数据的还是当前线程。

按照这个思路,AIO应该是发起IO读写的线程,和实际收到数据的线程,可能不是同一个线程 是不是这样呢,现在开始上Java AIO的代码。

2.3 Java AIO的程序示例

2.3.1 AIO服务端程序


public class AioServer {


    public static void main(String[] args) throws IOException {
        System.out.println(Thread.currentThread().getName() + " AioServer start");
        AsynchronousServerSocketChannel serverChannel = AsynchronousServerSocketChannel.open()
                .bind(new InetSocketAddress("127.0.0.1", 8080));
        serverChannel.accept(null, new CompletionHandler() {


            @Override
            public void completed(AsynchronousSocketChannel clientChannel, Void attachment) {
                System.out.println(Thread.currentThread().getName() + " client is connected");
                ByteBuffer buffer = ByteBuffer.allocate(1024);
                clientChannel.read(buffer, buffer, new ClientHandler());
            }


            @Override
            public void failed(Throwable exc, Void attachment) {
                System.out.println("accept fail");
            }
        });
        System.in.read();
    }
}


public class ClientHandler implements CompletionHandler {
    @Override
    public void completed(Integer result, ByteBuffer buffer) {
        buffer.flip();
        byte [] data = new byte[buffer.remaining()];
        buffer.get(data);
        System.out.println(Thread.currentThread().getName() + " received:"  + new String(data, StandardCharsets.UTF_8));
    }


    @Override
    public void failed(Throwable exc, ByteBuffer buffer) {


    }
}

2.3.2 AIO客户端程序

public class AioClient {


    public static void main(String[] args) throws Exception {
        AsynchronousSocketChannel channel = AsynchronousSocketChannel.open();
        channel.connect(new InetSocketAddress("127.0.0.1", 8080));
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        buffer.put("Java AIO".getBytes(StandardCharsets.UTF_8));
        buffer.flip();
        Thread.sleep(1000L);
        channel.write(buffer);
 }
}

2.3.3 异步的定义猜想结论

分别运行服务端和客户端程序

b0c449bc-c8e0-11ed-bfe3-dac502259ad0.png

在服务端运行结果里, main线程发起serverChannel.accept的调用,添加了一个CompletionHandler监听回调,当有客户端连接过来时,Thread-5线程执行了accep的completed回调方法。 紧接着Thread-5又发起了clientChannel.read调用,也添加了个CompletionHandler监听回调,当收到数据时,是Thread-1的执行了read的completed回调方法。

这个结论和上面异步猜想一致,发起IO操作(例如accept、read、write)调用的线程,和最终完成这个操作的线程不是同一个,我们把这种IO模式称之AIO, 当然了,这样定义AIO只是为了方便我们理解,实际中对异步IO的定义可能更抽象一点。

3

AIO示例引发思考的问题

1、 执行completed()方法的这个线程是谁创建的,什么时候创建的?

2、 AIO注册事件监听和执行回调是如何实现的?

3、 监听回调的本质是什么?

3.1 问题1:执行completed()方法的这个线程是谁创建的,什么时候创建的

一般,这样的问题,需要从程序的入口的开始了解,但跟线程相关,其实是可以从线程栈的运行情况来定位线程是怎么运行。 只运行AIO服务端程序,客户端不运行,打印一下线程栈(备注:程序在Linux平台上运行,其他平台略有差异)

b0dc3cfc-c8e0-11ed-bfe3-dac502259ad0.png

分析线程栈,发现,程序启动了那么几个线程

1、 线程Thread-0阻塞在EPoll.wait()方法上

2、 线程Thread-1、Thread-2。。。Thread-n(n和CPU核心数量一致)从阻塞队列里take()任务,阻塞等待有任务返回。 此时可以暂定下一个结论: AIO服务端程序启动之后,就开始创建了这些线程,且线程都处于阻塞等待状态。

另外,发现这些线程的运行都跟Epoll有关系,提到Epoll,我们印象中,Java NIO在Linux平台底层就是用Epoll来实现的,难道Java AIO也是用Epoll来实现么?为了证实这个结论,我们从下一个问题来展开讨论

3.2 问题2:AIO注册事件监听和执行回调是如何实现的

带着这个问题,去阅读分析源码时,发现源码特别的长,而源码解析是一项枯燥乏味的过程,很容易把阅读者给逼走劝退掉。

对于长流程和逻辑复杂的代码的理解,我们可以抓住它几个脉络,找出哪几个核心流程。

以注册监听read为例clientChannel.read(...),它主要的核心流程是: 1、注册事件 -> 2、监听事件 -> 3、处理事件

3.2.1 1、注册事件

b1688eaa-c8e0-11ed-bfe3-dac502259ad0.png

注册事件调用EPoll.ctl(...)函数,这个函数在最后的参数用于指定是一次性的,还是永久性。

上面代码events | EPOLLONSHOT字面意思看来,是一次性的。

3.2.2 2、监听事件

b1a00cae-c8e0-11ed-bfe3-dac502259ad0.png

3.2.3 3、处理事件

b1cb0a94-c8e0-11ed-bfe3-dac502259ad0.pngb1f79adc-c8e0-11ed-bfe3-dac502259ad0.pngb2315420-c8e0-11ed-bfe3-dac502259ad0.png

3.2.4 核心流程总结

b250eede-c8e0-11ed-bfe3-dac502259ad0.png

在分析完上面的代码流程后会发现,每一次IO读写都要经历的这三个事件是一次性的,也就是在处理事件完,本次流程就结束了,如果想继续下一次的IO读写,就得从头开始再来一遍。这样就会存在所谓的死亡回调(回调方法里再添加下一个回调方法),这对于编程的复杂度大大提高了。

3.3 问题3: 监听回调的本质是什么?

先说一下结论,所谓监听回调的本质,就是用户态线程,调用内核态的函数(准确的说是API,例如read,write,epollWait),该函数还没有返回时,用户线程被阻塞了。

当函数返回时,会唤醒阻塞的线程,执行所谓回调函数。
对于这个结论的理解,要先引入几个概念

3.3.1 系统调用与函数调用

函数调用: 找到某个函数,并执行函数里的相关命令 系统调用: 操作系统对用户应用程序提供了编程接口,所谓API。

系统调用执行过程:

1.传递系统调用参数

2.执行陷入指令,用用户态切换到核心态,这是因为系统调用一般都需要再核心态下执行

3.执行系统调用程序

4.返回用户态

3.3.2 用户态和内核态之间的通信

用户态->内核态,通过系统调用方式即可。 内核态->用户态,内核态根本不知道用户态程序有什么函数,参数是啥,地址在哪里。所以内核是不可能去调用用户态的函数,只能通过发送信号,比如kill 命令关闭程序就是通过发信号让用户程序优雅退出的。

既然内核态是不可能主动去调用用户态的函数,为什么还会有回调呢,只能说这个所谓回调其实就是用户态的自导自演。它既做了监听,又做了执行回调函数

3.3.3 用实际例子验证结论

为了验证这个结论是否有说服力,举个例子,平时开发写代码用的IntelliJ IDEA,它是如何监听鼠标、键盘事件和处理事件的。 按照惯例,先打印一下线程栈,会发现鼠标、键盘等事件的监听是由"AWT-XAWT"线程负责的,处理事件则是"AWT-EventQueue"线程负责。

b28d98de-c8e0-11ed-bfe3-dac502259ad0.png

定位到具体的代码上,可以看到"AWT-XAWT"正在做while循环,调用waitForEvents函数等待事件返回。如果没有事件,线程就一直阻塞在那边。

b2f3c492-c8e0-11ed-bfe3-dac502259ad0.png

4

Java AIO的本质是什么?

1、由于内核态无法直接调用用户态函数,Java AIO的本质,就是只在用户态实现异步。并没有达到理想意义上的异步。

理想中的异步 何谓理想意义上的异步?这里举个网购的例子 两个角色,消费者A,快递员B

A在网上购物时,填好家庭地址付款提交订单,这个相当于注册监听事件

商家发货,B把东西送到A家门口,这个相当于回调。

A在网上下完单,后续的发货流程就不用他来操心了,可以继续做其他事。B送货也不关心A在不在家,反正就把货扔到家门口就行了,两个人互不依赖,互不相干扰假设A购物是用户态来做,B送快递是内核态来做,这种程序运行方式过于理想了,实际中实现不了

现实中的异步 A住的是高档小区,不能随意进去,快递只能送到小区门口。 A买了一件比较重的商品,比如一台电视,因为A要上班不在家里,所以找了一个好友C帮忙把电视搬到他家。 A出门上班前,跟门口的保安D打声招呼,说今天有一台电视送过来,送到小区门口时,请电话联系C,让他过来拿。

此时,A下单并跟D打招呼,相当于注册事件。在AIO中就是EPoll.ctl(...)注册事件。

保安在门口蹲着相当于监听事件,在AIO中就是Thread-0线程,做EPoll.wait(..)

快递员把电视送到门口,相当于有IO事件到达。

保安通知C电视到了,C过来搬电视,相当于处理事件。在AIO中就是Thread-0往任务队列提交任务,Thread-1 ~n去取数据,并执行回调方法。

整个过程中,保安D必须一直蹲着,寸步不能离开,否则电视送到门口,就被人偷了。 好友C也必须在A家待着,受人委托,东西到了,人却不在现场,这有点失信于人。

所以实际的异步和理想中的异步,在互不依赖,互不干扰,这两点相违背了。保安的作用最大,这是他人生的高光时刻。 异步过程中的注册事件、监听事件、处理事件,还有开启多线程,这些过程的发起者全是用户态一手操办,所以说Java AIO只在用户态实现了异步,这个和BIO、NIO先阻塞,阻塞唤醒后开启异步线程处理的本质一致。

2、Java AIO跟NIO一样,在各个平台的底层实现方式也不同,在Linux是用EPoll,Windows是IOCP,Mac OS是KQueue。原理是大同小异,都是需要一个用户线程阻塞等待IO事件,一个线程池从队列里处理事件。

3、 Netty之所以移除掉AIO,很大的原因是在性能上AIO并没有比NIO高。Linux虽然也有一套原生的AIO实现(类似Windows上的IOCP),但Java AIO在Linux并没有采用,而是用EPoll来实现。

4、 Java AIO不支持UDP

5、 AIO编程方式略显复杂,比如“死亡回调”






审核编辑:刘清

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • Linux
    +关注

    关注

    87

    文章

    11217

    浏览量

    208818
  • JAVA
    +关注

    关注

    19

    文章

    2954

    浏览量

    104511
  • AIO
    AIO
    +关注

    关注

    1

    文章

    61

    浏览量

    9964
  • Thread
    +关注

    关注

    2

    文章

    83

    浏览量

    25898

原文标题:透过现象看Java AIO的本质

文章出处:【微信号:OSC开源社区,微信公众号:OSC开源社区】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    BIO、NIOAIO 模型工作方式

    一、简介 在计算机中,IO 传输数据有三种工作方式,分别是: BIO、NIOAIO 。 在讲解 BIO、NIOAIO 之前,我们先来回顾一下这几个概念: 同步与异步,阻塞与非阻塞
    的头像 发表于 09-30 10:43 461次阅读
    BIO、<b class='flag-5'>NIO</b>、<b class='flag-5'>AIO</b> 模型工作方式

    IO与NIO有何区别

    NIO 提到IO,这是Java提供的一套类库,用于支持应用程序与内存、文件、网络间进行数据交互,实现数据写入与输出。JDK自从1.4版本后,提供了另一套类库NIO,我们平时习惯称呼为N
    的头像 发表于 09-25 11:00 824次阅读
    IO与<b class='flag-5'>NIO</b>有何区别

    Java NIO编程理论基础之Java IO及linux网络IO模型发展

    Java NIO编程理论基础篇——Java IO的发展以及linux网络IO模型
    发表于 07-18 12:40

    Java NIO反应器模式设计

    Javal.4引入的NIO包里,最引人注目的是加入了非阻塞I/O。和IO包提供的阻塞模型不同,NIO在对一个非阻塞的连接进行操作时,调用会立即返回,而不是挂起等待.
    发表于 01-05 16:06 38次下载
    <b class='flag-5'>Java</b> <b class='flag-5'>NIO</b>反应器模式设计

    Java NIO (中文版)编程总结

    Java NIO 编程总结
    发表于 09-21 11:17 0次下载

    JavaNIO的基本概念

    一.NIO中的几个基础概念 在NIO中有几个比较关键的概念:Channel(通道),Buffer(缓冲区),Selector(选择器)。 首先从Channel说起吧,通道,顾名思义,就是通向什么的
    发表于 09-27 10:44 0次下载
    <b class='flag-5'>Java</b>之<b class='flag-5'>NIO</b>的基本概念

    (NIO)为何缘故,要大规模裁员?

    在网名“纸孩子”率先爆料蔚(NIO)大规模裁员和回租等系列内幕后,蔚汽车以一份无章、无抬头的回应“辟谣”。
    发表于 03-27 15:54 1478次阅读

    推送L2级别自动辅助驾驶功能和NIO OS 2.0智能操作系统

    6月10日,NIO Pilot自动辅助驾驶系统迎来升级,本次新增7项功能;同时推送升级的还有NIO OS 2.0智能操作系统,全新的UX交互和全新的UI设计将带来全新的数字化体验。
    发表于 06-17 09:12 1213次阅读

    NIO OS 2.5.0版本升级 NOMI体验更加合理和完善

    2月20日,蔚发布了NIO OS 2.5.0版本的升级,也是进入NIO OS 2.0阶段第5次大升级,将会陆续通过FOTA对当前版本为2.
    发表于 03-08 08:59 3080次阅读

    JAVANIO通过MappedByteBuffer操作大文件

    java io操作中通常采用BufferedReader,BufferedInputStream等带缓冲的IO类处理大文件,不过java nio中引入了一种基于MappedByteBuffer操作大文件的方式,其读写性能极高,本
    的头像 发表于 05-05 23:42 3476次阅读

    NIO Day发布的新产品都有哪些亮点?

    而此前经多轮预热、成为焦点的蔚新品发布环节,却只占将近三个小时NIO Day的1/4时间,以至于被外界戏称为“真的只是一个‘节目’”。
    的头像 发表于 01-12 10:38 2715次阅读

    NIO Phone发布:蔚风格,旗舰选择

    NIO Phone 亮点一览 第二代骁龙8领先版 标志性天际线设计与纯粹系统 高通3D Sonic Max超声波指纹传感器 5000万像素三主摄 以车为中心的移动互联全新体验 今日,在2023蔚
    的头像 发表于 09-22 00:00 671次阅读
    蔚<b class='flag-5'>来</b><b class='flag-5'>NIO</b> Phone发布:蔚<b class='flag-5'>来</b>风格,旗舰选择

    手机9月28日开始发货 NIO Phone起售价6499

    手机9月28日开始发货 NIO Phone起售价6499 蔚造手机、小米造车,不知道以后华为会要造什么?或者苹果造车? 蔚来用户需要一款与蔚汽车无缝连接的手机。所以蔚
    的头像 发表于 09-26 18:39 779次阅读

    NIO Phone体验报告:智能体验超出预期,车手互联颇有惊喜

    来了专为车主打造的NIO Link蔚全景互联技术,实现了车机与手机的生态系统闭环。本期体验报告,不妨一起来看看NIO Phone带来的多项创新惊喜吧。 蔚
    的头像 发表于 12-15 20:45 1204次阅读
    蔚<b class='flag-5'>来</b><b class='flag-5'>NIO</b> Phone体验报告:智能体验超出预期,车手互联颇有惊喜

    宣布完成NIO Phone 2研发,每年仅发布一款新机

    值得注意的是,早在2023年9月的蔚创新科技日,李斌曾在接受媒体采访时透露,蔚手机会持续迭代,研发周期已形成“固定模式”,第二代NIO Phone正处于研发当中。而在同年12月,蔚来自家推出首款智能手机——蔚
    的头像 发表于 03-19 15:40 1276次阅读