Java中NIO,BIO,AIO的原理以及区别

2023-11-11

在今天的面试中,遇到一个复旦大学计算机博士,这确实牛逼一直问Java底层,问的我直冒汗,今天把问道我的分享出来,今天问到了NIO,BIO,AIO之间的原理和关系,我只知道阻塞和非阻塞之类的,具体的区别不是很清楚。所以查询资料好好复习下他们之间的原理.
IO 介绍
通常所说的 BIO 是相对于 NIO 来说的,BIO 也就是 Java 开始之初推出的 IO 操作模块,BIO 是 BlockingIO 的缩写,顾名思义就是阻塞 IO 的意思

BIO、NIO、AIO的区别

  • BIO
    BIO 就是传统的 java.io 包,它是基于流模型实现的,交互的方式是同步、阻塞方式,也就是说在读入输入流或者输出流时,在读写动作完成之前,线程会一直阻塞在那里,它们之间的调用时可靠的线性顺序。它的有点就是代码比较简单、直观;缺点就是 IO 的效率和扩展性很低,容易成为应用性能瓶颈;这里使用那个经典的烧开水例子,这里假设一个烧开水的场景,有一排水壶在烧开水,BIO的工作模式就是, 叫一个线程停留在一个水壶那,直到这个水壶烧开,才去处理下一个水壶。但是实际上线程在等待水壶烧开的时间段什么都没有做。
  • NIO
    同时支持阻塞与非阻塞模式,但这里我们以其同步非阻塞I/O模式来说明,那么什么叫做同步非阻塞?如果还拿烧开水来说,NIO的做法是叫一个线程不断的轮询每个水壶的状态,看看是否有水壶的状态发生了改变,从而进行下一步的操作;NIO 是 Java 1.4 引入的 java.nio 包,提供了 Channel、Selector、Buffer 等新的抽象,可以构建多路复用的、同步非阻塞 IO 程序,同时提供了更接近操作系统底层高性能的数据操作方式。
    NIO基于Reactor,当socket有流可读或可写入socket时,操作系统会相应的通知引用程序进行处理,应用再将流读取到缓冲区或写入操作系统。 也就是说,这个时候,已经不是一个连接就要对应一个处理线程了,而是有效的请求,对应一个线程,当连接没有数据时,是没有工作线程来处理的。
    BIO与NIO一个比较重要的不同,是我们使用BIO的时候往往会引入多线程,每个连接一个单独的线程;而NIO则是使用单线程或者只使用少量的多线程,每个连接共用一个线程。
    NIO的最重要的地方是当一个连接创建后,不需要对应一个线程,这个连接会被注册到多路复用器上面,所以所有的连接只需要一个线程就可以搞定,当这个线程中的多路复用器进行轮询的时候,发现连接上有请求的话,才开启一个线程进行处理,也就是一个请求一个线程模式。
    在NIO的处理方式中,当一个请求来的话,开启线程进行处理,可能会等待后端应用的资源(JDBC连接等),其实这个线程就被阻塞了,当并发上来的话,还是会有BIO一样的问题。
    HTTP/1.1出现后,有了Http长连接,这样除了超时和指明特定关闭的http header外,这个链接是一直打开的状态的,这样在NIO处理中可以进一步的进化,在后端资源中可以实现资源池或者队列,当请求来的话,开启的线程把请求和请求数据传送给后端资源池或者队列里面就返回,并且在全局的地方保持住这个现场(哪个连接的哪个请求等),这样前面的线程还是可以去接受其他的请求,而后端的应用的处理只需要执行队列里面的就可以了,这样请求处理和后端应用是异步的.当后端处理完,到全局地方得到现场,产生响应,这个就实现了异步处理。
  • AIO
    异步非阻塞I/O模型。异步非阻塞与同步非阻塞的区别在哪里?异步非阻塞无需一个线程去轮询所有IO操作的状态改变,在相应的状态改变后,系统会通知对应的线程来处理。对应到烧开水中就是,为每个水壶上面装了一个开关,水烧开之后,水壶会自动通知我水烧开了。
    与NIO不同,当进行读写操作时,只须直接调用API的read或write方法即可。这两种方法均为异步的,对于读操作而言,当有流可读取时,操作系统会将可读的流传入read方法的缓冲区,并通知应用程序;对于写操作而言,当操作系统将write方法传递的流写入完毕时,操作系统主动通知应用程序。 即可以理解为,read/write方法都是异步的,完成后会主动调用回调函数。 在JDK1.7中,这部分内容被称作NIO.2,主要在java.nio.channels包下增加了下面四个异步通道:
    AsynchronousSocketChannelAsynchronousServerSocketChannel
    AsynchronousFileChannel;AsynchronousDatagramChannel
    BIO是一个连接一个线程。
    NIO是一个请求一个线程。
    AIO是一个有效请求一个线程。
    先来个例子理解一下概念,以银行取款为例:
  • 同步 : 自己亲自出马持银行卡到银行取钱(使用同步IO时,Java自己处理IO读写);
  • 异步: 委托一小弟拿银行卡到银行取钱,然后给你(使用异步IO时,Java将IO读写委托给OS处理,需要将数据缓冲区地址和大小传给OS(银行卡和密码),OS需要支持异步IO操作API);
  • 阻塞 : ATM排队取款,你只能等待(使用阻塞IO时,Java调用会一直阻塞到读写完成才返回);
  • 非阻塞 : 柜台取款,取个号,然后坐在椅子上做其它事,等号广播会通知你办理,没到号你就不能去,你可以不断问大堂经理排到了没有,大堂经理如果说还没到你就不能去(使用非阻塞IO时,如果不能读写Java调用会马上返回,当IO事件分发器会通知可读写时再继续进行读写,不断循环直到读写完成)
  • Java对BIO、NIO、AIO的支持:
    Java BIO : 同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
    Java NIO : 同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。
    Java AIO(NIO.2) : 异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理,
    -BIO、NIO、AIO适用场景分析:
    BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。
    NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。
    AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。

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

一般来说I/O模型可以分为:同步阻塞,同步非阻塞,异步阻塞,异步非阻塞IO.

  • 同步与异步
    同步就是一个任务的完成需要依赖另外一个任务时,只有等待被依赖的任务完成后,依赖的任务才能算完成,这是一种可靠的任务序列。要么成功都成功,失败都失败,两个任务的状态可以保持一致。而异步是不需要等待被依赖的任务完成,只是通知被依赖的任务要完成什么工作,依赖的任务也立即执行,只要自己完成了整个任务就算完成了。至于被依赖的任务最终是否真正完成,依赖它的任务无法确定,所以它是不可靠的任务序列。我们可以用打电话和发短信来很好的比喻同步与异步操作。

  • 阻塞与非阻塞
    阻塞与非阻塞主要是从 CPU 的消耗上来说的,阻塞就是 CPU 停下来等待一个慢的操作完成 CPU 才接着完成其它的事。非阻塞就是在这个慢的操作在执行时 CPU 去干其它别的事,等这个慢的操作完成时,CPU 再接着完成后续的操作。虽然表面上看非阻塞的方式可以明显的提高 CPU 的利用率,但是也带了另外一种后果就是系统的线程切换增加。增加的 CPU 使用时间能不能补偿系统的切换成本需要好好评估。同/异、阻/非堵塞的组合,有四种类型,如下表:

    同步阻塞IO:在此种方式下,用户进程在发起一个IO操作以后,必须等待IO操作的完成,只有当真正完成了IO操作以后,用户进程才能运行。JAVA传统的IO模型属于此种方式!
    同步非阻塞IO:在此种方式下,用户进程发起一个IO操作以后边可返回做其它事情,但是用户进程需要时不时的询问IO操作是否就绪,这就要求用户进程不停的去询问,从而引入不必要的CPU资源浪费。其中目前JAVA的NIO就属于同步非阻塞IO。
    异步阻塞IO:此种方式下是指应用发起一个IO操作以后,不等待内核IO操作的完成,等内核完成IO操作以后会通知应用程序,这其实就是同步和异步最关键的区别,同步必须等待或者主动的去询问IO是否完成,那么为什么说是阻塞的呢?因为此时是通过select系统调用来完成的,而select函数本身的实现方式是阻塞的,而采用select函数有个好处就是它可以同时监听多个文件句柄,从而提高系统的并发性!
    异步非阻塞IO:在此种模式下,用户进程只需要发起一个IO操作然后立即返回,等IO操作真正的完成以后,应用程序会得到IO操作完成的通知,此时用户进程只需要对数据进行处理就好了,不需要进行实际的IO读写操作,因为真正的IO读取或者写入操作已经由内核完成了。目前Java中还没有支持此种IO模型。

    Socket 和 NIO 的多路复用

    • 传统的 Socket 实现
      接下来我们将会实现一个简单的 Socket,服务器端只发给客户端信息,再由客户端打印出来的例子,代码如下
int port = 4343; //端口号
// Socket 服务器端(简单的发送信息)
Thread sThread = new Thread(new Runnable() {
    @Override
    public void run() {
        try {
            ServerSocket serverSocket = new ServerSocket(port);
            while (true) {
                // 等待连接
                Socket socket = serverSocket.accept();
                Thread sHandlerThread = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try (PrintWriter printWriter = new PrintWriter(socket.getOutputStream())) {
                            printWriter.println("hello world!");
                            printWriter.flush();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                });
                sHandlerThread.start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
});
sThread.start();

// Socket 客户端(接收信息并打印)
try (Socket cSocket = new Socket(InetAddress.getLocalHost(), port)) {
    BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(cSocket.getInputStream()));
    bufferedReader.lines().forEach(s -> System.out.println("客户端:" + s));} catch (UnknownHostException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
}
  • 调用 accept 方法,阻塞等待客户端连接;
  • 利用 Socket 模拟了一个简单的客户端,只进行连接、读取和打印;
    在 Java 中,线程的实现是比较重量级的,所以线程的启动或者销毁是很消耗服务器的资源的,即使使用线程池来实现,使用上述传统的 Socket 方式,当连接数极具上升也会带来性能瓶颈,原因是线程的上线文切换开销会在高并发的时候体现的很明显,并且以上操作方式还是同步阻塞式的编程,性能问题在高并发的时候就会体现的尤为明显,该流程如同所示:

NIO 多路复用

介于以上高并发的问题,NIO 的多路复用功能就显得意义非凡了。
NIO 是利用了单线程轮询事件的机制,通过高效地定位就绪的 Channel,来决定做什么,仅仅 select 阶段是阻塞的,可以有效避免大量客户端连接时,频繁线程切换带来的问题,应用的扩展能力有了非常大的提高。

// NIO 多路复用
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(4, 4,
        60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
threadPool.execute(new Runnable() {
    @Override
    public void run() {
        try (Selector selector = Selector.open();
             ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();) {
            serverSocketChannel.bind(new InetSocketAddress(InetAddress.getLocalHost(), port));
            serverSocketChannel.configureBlocking(false);
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            while (true) {
                selector.select(); // 阻塞等待就绪的Channel
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> iterator = selectionKeys.iterator();
                while (iterator.hasNext()) {
                    SelectionKey key = iterator.next();
                    try (SocketChannel channel = ((ServerSocketChannel) key.channel()).accept()) {
                        channel.write(Charset.defaultCharset().encode("你好,世界"));
                    }
                    iterator.remove();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
});
// Socket 客户端(接收信息并打印)
try (Socket cSocket = new Socket(InetAddress.getLocalHost(), port)) {
    BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(cSocket.getInputStream()));
    bufferedReader.lines().forEach(s -> System.out.println("NIO 客户端:" + s));
} catch (IOException e) {
    e.printStackTrace();
}
  • 首先,通过 Selector.open() 创建一个 Selector,作为类似调度员的角色;
  • 然后,创建一个 ServerSocketChannel,并且向 Selector 注册,通过指定 SelectionKey.OP_ACCEPT,告诉调度员,它关注的是新的连接请求;
  • 为什么我们要明确配置非阻塞模式呢?这是因为阻塞模式下,注册操作是不允许的,会抛出 IllegalBlockingModeException 异常;
  • Selector 阻塞在 select 操作,当有 Channel 发生接入请求,就会被唤醒;
    下面的图,可以有效的说明 NIO 复用的流程:

    就这样 NIO 的多路复用就大大提升了服务器端响应高并发的能力。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java中NIO,BIO,AIO的原理以及区别 的相关文章

随机推荐

  • proxy_set_header导致跨域失败分析

    跨域失败分析 proxy set header Host host 这个配置导致跨域失败 配置的作用是把原http请求的header中的host字段 即域名 放到转发请求里面 解决方案 配置成 proxy set header Host t
  • 【STM32技巧】STM32 CubeMX中定时器四种从机模式说明

    四种从机模式说明 外部时钟模式1从模式 External Clock Mod1 这个模式比较特别 名字也有点奇葩 其实 这个从模式跟时钟源的外部时钟模式1有渊源 在介绍计数器时钟源时 讲到过外部时钟模式1 即计数器的时钟来自TI1或TI2的
  • Java多线程工具包java.util.concurrent---ExecutorService

    什么是ExecutorService java util concurrent ExecutorService 接口表示一个异步执行机制 使我们能够在后台执行任务 因此一个 ExecutorService 很类似于一个线程池 实际上 存在于
  • 编译原理 实验四 LR(1)分析法程序

    源代码仓库 CompilePrincipleLearning experiment 4 yusixian CompilePrincipleLearning github com 源代码在demo文件夹中 一 实验目的 掌握LR 1 分析法的
  • 好的博客

    RabbitMQ http www ityouknow com springboot 2016 11 30 spring boot rabbitMQ html https www kancloud cn ichenpeng blog 109
  • Renesas瑞萨电子提供的RcarH3,第一个SOC从第三代R-Car汽车计算平台的自动驾驶领域时代

    e mail 174176320 qq com 瑞萨电子株式会社 东京证交所 6723 一个英超的先进半导体解决方案供应商 今天宣布推出第三代Rcar 汽车计算平台解决方案的安全驾驶辅助系统和车载信息娱乐系统 芯片上的新的RcarH3系统
  • 【Vue2.0源码学习】虚拟DOM篇-Vue中的DOM-优化更新子节点

    1 前言 在上一篇文章中 我们介绍了当新的VNode与旧的oldVNode都是元素节点并且都包含子节点时 Vue对子节点是 先外层循环newChildren数组 再内层循环oldChildren数组 每循环外层newChildren数组里的
  • PaddleDetection算法分析(14)

    2021SC SDUSC 三 SSD训练过程 源码如下 def vgg cfg i batch norm False 该代码参考vgg官网的代码 layers in channels i for v in cfg 正常的 max pooli
  • Spring——面向切面编程(AOP)

    1 AOP概述 AOP 并不是 Spring 框架的专属名称 它的全称是 Aspect Oriented Programming 意为 面向切面编程 在程序运行某个方法的时候 不修改原始执行代码逻辑 由程序动态地执行某些额外的功能 对原有的
  • stata怎么判断是否存在异常值_如何用SPSS探测及检验异常值

    如何用 SPSS 探测及检验异常值 一 采用数据探索过程探测异常值 SPSS 菜单实现程序为 主菜单 gt Analyze gt Descriptive Statistics gt Explore 选项 gt Statistics 按钮 g
  • Learning Efficient Convolutional Networks through Network Slimming

    该文章是ICCV 2017的一篇模型压缩论文 提出了一个针对BN层的剪枝方法 利用BN层的权重 即缩放系数 来评估输入通道的重要程度 score 然后对score对于阈值的通道进行过滤 之后在连接成剪枝后的网络时 已经过滤的通道的神经元就不
  • Android Studio创建无图标应用 Default Activity not fount

    在Android Studio开发 如果你想创建一个没有没有应用图标的应用 你会发现 修改category修改为default 项目中没有一个Activity是Laucnher的话 没法运行 直接报Default Activity not
  • NLP学习(十二)-NLP实战之LSTM进行文本情感分析-tensorflow2+Python3

    情感分析简介 文本情感分析 Sentiment Analysis 是自然语言处理 NLP 方法中常见的应用 也是一个有趣的基本任务 尤其是以提炼文本情绪内容为目的的分类 它是对带有情感色彩的主观性文本进行分析 处理 归纳和推理的过程 本文将
  • JavaWeb——邮件发送原理及实现

    邮件发送原理及实现 一 邮件发送原理图 张三通过smtp协议连接到Smtp服务器 然后发送一封邮件给网易的邮件服务器 网易分析发现需要去QQ的邮件服务器 通过Smtp协议将邮件转投给QQ的Smtp服务器 QQ将接收到的邮件存储在456789
  • 【动手学习pytorch笔记】37.4 BERT微调数据集

    BERT微调数据集 自然语言推断任务 主要研究 假设 hypothesis 是否可以从前提 premise 中推断出来 其中两者都是文本序列 换言之 自然语言推断决定了一对文本序列之间的逻辑关系 这类关系通常分为三种类型 蕴涵 entail
  • 算法中的双指针思想及常见应用

    算法中的双指针思想及常见应用 最近在刷leetcode 碰到了许多双指针类的题目 题目是根据githubCyC大佬 在这里总结下 所谓双指针 指的是在遍历对象的过程中 不是普通的使用单个指针进行访问 而是使用两个相同方向或者相反方向的指针进
  • mybatis执行自定义SQL语句

    在mybatis中执行自定义SQL语句 本次仍使用spring boot 其中关于引入mybatis和数据库配置不再说明 UserInfoMapper xml 映射文件
  • 404 not found是什么意思

    相信很多人都会遇到404 也有很多人遇到404不知道是什么意思 也不知道怎么解决 下面我们php中文网就为大家全面解答一下404 not found是什么意思 打造全网web前端全栈资料库 总目录 看完学的更快 掌握的更加牢固 你值得拥有
  • Qt窗口间信号发送和槽函数返回值

    新建工程 Qt Widgets Application 类名为Widget 在这个基础上新建Qt设计师界面类 类名Dialog widget ui和dialog ui里分别拉入一个QPushbutton widget ui里再拉入一个QLi
  • Java中NIO,BIO,AIO的原理以及区别

    在今天的面试中 遇到一个复旦大学计算机博士 这确实牛逼一直问Java底层 问的我直冒汗 今天把问道我的分享出来 今天问到了NIO BIO AIO之间的原理和关系 我只知道阻塞和非阻塞之类的 具体的区别不是很清楚 所以查询资料好好复习下他们之