BIO、NIO、AIO理解_bio nio aio-程序员宅基地

技术标签: 经验分享  面试  nio  

一、到底什么是BIO、NIO、AIO?

这些可以理解为是Java语言对操作系统的各种IO模型的封装,程序员在使用这些API的时候,不需要关系操作系统层面的知识,也不需要根据不同操作系统编写不同的代码,只需要使用Java的API就可以了。

二、BIO、NIO、AIO的区别

1.BIO就是传统的java.io包(意思就是你在使用java.io包进行输入输出操作的时候,就是使用的BIO通信机制),BIO是传统的同步阻塞式的I/O。也就是说在读入输入流或者输出流时,在读写操作完成之前,线程会一直阻塞,会一直占用CPU资源,直到读写操作完成之后,才继续完成下面的任务。因此每个请求都需要有一个线程来单独处理。

采用BIO通信模型的服务端,通常有一个独立的Acceptor线程负责监听客户端的连接,它接收到客户端的连接请求之后,会为客户端每一个连接请求创建一个新的线程进行处理,处理完之后,通过输出流返回给客户端,线程销毁,这就是典型的一请求一应答模型。

 可以看出,这是在多线程情况下执行的。当在单线程环境条件下,在while循环中服务端会调用accept方法等待接收客户端的连接请求,一旦收到这个连接请求,就可以建立socket,并在socket上进行读写操作,此时不能再接收其他客户端的连接请求,只能等待同当前服务端连接的客户端的操作完成或者连接断开。

该模型最大的缺单就是缺乏弹性伸缩能力,当客户端并发访问量增加后,服务端的线程个数和客户端并发访问数呈1:1的正比关系,由于线程是java虚拟机非常宝贵的系统资源,当线程数膨胀之后,系统的性能会急剧下降,随着并发量的继续增大,系统会发生线程堆栈溢出、创建新线程失败等问题,并最终导致进程宕机或者僵死,不能对外提供服务。

BIO实现的代码:

public class server {
    private static Socket socket=null;
    public static void main(String[] args) {
        try {
            //绑定端口
            ServerSocket serverSocket=new ServerSocket();
            serverSocket.bind(new InetSocketAddress(8080));
            while (true){
                //等待连接  阻塞
                System.out.println("等待连接");
                socket = serverSocket.accept();
                System.out.println("连接成功");
                //连接成功后新开一个线程去处理这个连接
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        byte[] bytes=new byte[1024];
                        try {
                            System.out.println("等待读取数据");
                            //等待读取数据    阻塞
                            int length=socket.getInputStream().read(bytes);
                            System.out.println(new String(bytes,0,length));
                            System.out.println("数据读取成功");
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

2.NIO是Java 1.4引入的java.nio包,提供了Channel、Selector、Buffer等新的抽象,可以构建多路复用的、同步非阻塞式IO程序,同时提供了更接近操作系统底层高性能的数据操作方式。NIO通过使用单线程轮询多个连接的的方式来实现高效的处理方式,可以支持较大数量的并发连接,但编程模型较为复杂。

NIO是为了解决BIO的缺陷提出的通信模型,以socket.read()为例:

传统的BIO里面的socket.read(),如果TCP RecvBuffer里没有数据,函数会一直阻塞,直到收到数据。

对于NIO,如果TCP RecvBuffer有数据,就把数据从网卡读到内存,并且返回给用户;反之则直接返回0,永远不会阻塞。

BIO关心的是“我要读”的问题,NIO关心的是“我可以读了”的问题。

NIO一个重要的特点是:socket主要的读、写、注册和接收函数,在等待就绪阶段都是非阻塞的,真正的I/O操作是同步阻塞的(消耗CPU但性能非常高)。

2.1 什么是Channel?

Channel,翻译过来就是“通道”,就是数据传输的管道,类似于“流”,但是与“流”又有着区别。

Channel与“流”的区别:

  • 既可以从Channel中读取数据,又可以写数据到Channel,但流的读写通常是单向的——输入输出流
  • 通道可以异步读写
  • 通道中的数据总是先读取到buffer(缓冲区),或者总是需要从一个buffer写入,不能直接访问数据
为什么Channel支持异步读写?

Java的Channel可以实现异步读写的原因主要有以下几点:

  1. 非阻塞特性:Channel在设计上采用了非阻塞的特性,它不会像传统的流一样在读写操作上阻塞线程,而是立即返回结果,告诉调用者当前的状态。这使得程序可以在等待数据准备的过程中同时进行其他操作,实现了非阻塞IO。

  2. 事件通知机制:Channel通常搭配选择器(Selector)来使用,选择器能够检测多个Channel的就绪状态,如是否可读、可写等,并通过事件通知(例如轮询或回调)及时地通知程序哪些Channel处于就绪状态,从而可以进行相应的读写操作。这种机制支持程序实现异步IO模型。

  3. 操作系统底层支持:Channel的异步读写也依赖于操作系统底层的异步IO支持。Java NIO中的Channel实际上是对操作系统底层异步IO的封装和抽象,利用了操作系统提供的异步IO机制来实现其自身的异步读写功能。

2.2 什么是Buffer?

Buffer是一个对象,里面是要写入或者读出的数据,在java.nio库中,所有的数据都是用缓冲区处理的。

在读取数据时,它是直接读到缓冲区中的;在写入数据时,也是直接写到缓冲区中,任何时候访问Channel中的数据,都是通过缓冲区进行操作的。

缓冲区实质上是一个数组,通常是一个字节数组ByteBuffer,当然也有其他类型的:

2.3 什么是Selector? 

Selector被称为选择器,Selector会不断地轮询注册在其上的Channel,如果某个Channel上发生读或写事件,这个Channel就被判定处于就绪状态,会被Selector轮询出来,然后通过SelectionKey可以获取到就绪Channel的集合,进行后续的I/O操作。

一个多路复用器Selector可以同时轮询多个Channel,JDK使用了epoll()代替了传统的select实现,所以并没有最大连接句柄的限制,这意味着只需要一个线程负责Selector的轮询,就可以接入成千上万的客户端。

一些名词解释

select实现

select方法是一种用于实现IO多路复用的系统调用,在Unix/Linux操作系统中广泛使用。其主要作用是监视多个文件描述符,当其中任何一个文件描述符就绪(可读、可写或异常)时,通知程序进行相应的IO操作。以下是select方法的基本介绍:

  1. 功能:select方法允许程序同时监视多个文件描述符的IO状态,包括可读、可写和异常状态,从而实现IO多路复用,提高程序的效率。

  2. 使用:程序需要将需要监视的文件描述符集合传递给select方法,并设置超时时间(可以设为NULL表示一直等待)。select方法会阻塞程序,直到其中一个文件描述符就绪或超时为止。

  3. 返回值:select方法返回就绪的文件描述符数量,如果超时则返回0,如果出错则返回-1。程序可以通过检查文件描述符集合的状态来确定哪些文件描述符处于就绪状态。

  4. 限制:select方法在监视文件描述符时有最大连接句柄数限制,一般不能超过1024个文件描述符。这限制了select方法在处理大规模并发连接时的效率。

  5. 缺点:select方法由于使用位图来表示文件描述符集合,需要线性遍历所有文件描述符,导致效率降低,特别是在大量文件描述符情况下。

总的来说,select方法是一种经典的IO多路复用机制,虽然存在一些限制和缺点,但在某些场景下仍然具有一定的应用价值。对于大规模并发连接的情况,通常会选择更高效的方法,比如epoll。

位图

位图(Bitmap)是一种数据结构,用于表示若干个比特位的集合。在计算机中,每一位(bit)可以只有两个取值,通常是0或1,因此位图实际上是一个由0和1组成的序列,用来表示某些状态或信息。

举例说明,如果我们有8个开关,每个开关只有两种状态:打开或关闭。我们可以使用一个8位的位图来表示这些开关的状态情况。比如,假设我们想表示这8个开关的状态如下:

  • 01101001

其中每一位代表一个开关,0表示关闭,1表示打开。通过这个位图,我们可以清晰地知道每个开关当前的状态,而且用8个bit就能表示8个开关的状态,非常紧凑高效。

在计算机科学中,位图常常被用于处理大规模的数据或进行高效的状态存储。它可以用于表示集合、存储标记、进行快速搜索和过滤等操作。在文件系统、数据库索引、图形处理等领域,位图都有着广泛的应用。

文件描述符

2.4 Buffer、Selector、Channel之间的关系?

 2.5 NIO多路复用的实现

NIO是利用了单线程轮询事件的机制,通过高效地定位就绪的Channel,来决定做什么,仅仅select阶段是阻塞的,可以有效避免大量客户端连接时,频繁切换线程带来的问题,应用的拓展能力有了很大的提高。

  • 首先,通过Selector.open()创建一个Selector,作为类似调度员的角色;
  • 然后,创建一个ServerSocketChannel,并在Selector中注册这个Channel,通过指定的SelectionKey.OP_ACCEPT,告诉调度员,它关注的是新的连接请求;
  • 为什么我们要明确配置非阻塞模式呢?这是因为阻塞模式下,注册模式是不允许的,会抛出IIIegalBlockingModeException异常;
  • Selector阻塞在select操作,当有Channel发生接入请求,就会被唤醒;
public class server {
    public static void main(String[] args) {
        try {
            //创建一个socket通道,并且设置为非阻塞的方式
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.configureBlocking(false);//设置为非阻塞的方式
            serverSocketChannel.socket().bind(new InetSocketAddress(9000));
            //创建一个selector选择器,把channel注册到selector选择器上
            Selector selector = Selector.open();
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            while(true)
            {
                System.out.println("等待事件发生");
                selector.select();
                System.out.println("有事件发生了");
                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                while(iterator.hasNext())
                {
                    SelectionKey key = iterator.next();
                    iterator.remove();
                    handle(key);
                }

            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private static void handle(SelectionKey key) throws IOException{
        if(key.isAcceptable())
        {
            System.out.println("连接事件发生");
            ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
            //创建客户端一侧的channel,并注册到selector上
            SocketChannel socketChannel = serverSocketChannel.accept();
            socketChannel.configureBlocking(false);
            socketChannel.register(key.selector(),SelectionKey.OP_READ);
        } else if (key.isReadable()) {
            System.out.println("数据可读事件发生");
            SocketChannel socketChannel = (SocketChannel) key.channel();
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            int len = socketChannel.read(buffer);
            if(len!=-1)
            {
                System.out.println("读取到客户端发送的数据:"+new String(buffer.array(),0,len));
            }
            //给客户端发送信息
            ByteBuffer wrap = ByteBuffer.wrap("hello world".getBytes());
            socketChannel.write(wrap);
            key.interestOps(SelectionKey.OP_READ|SelectionKey.OP_WRITE);
            socketChannel.close();
        }
    }

下面是多路复用的流程图:

 3.AIO是Java 1.7之后引入的包,是NIO的升级版本。异步非阻塞I/O,通过回调的方式实现高效的I/O操作,也就是应用操作之后会直接返回,不会堵塞(此时用户进程只需要对数据处理,而不需要进行实际的IO读写操作,因为真正的IO操作已经由操作系统内核完成了),当后台去处理完成之后,操作系统会通知相应的线程进行后续的操作。这样可以大大降低系统资源的消耗,适用于高并发、高吞吐量的场景,但在实际使用中可能会受到操作系统和硬件的限制。

4.可以用一个例子来描述这三个通信模型的区别:设想你要烧水这样一个场景

BIO:在烧水期间,你一直守在旁边,不敢任何事,等到水烧开了才去完成其他事

NIO:在烧水期间,你不必一直守在旁边,而是时不时来看水是否烧开,其他时间可以去完成其他事

AIO:在烧水期间,你无需来看水是否烧开了,可以去完成其他任务,水烧开的时候会发出提示音提示你水开了,这时候你再来处理。

三、一些基本概念的补充

同步和异步(线程之间的调用):

同步操作时,调用者需要等待被调用者返回结果,才会进行下一步操作

而异步则相反,调用者不需要等待被调用者返回调用,即可进行下一步操作,被调用者通常依靠事件、回调等机制来通知调用者返回调用结果

阻塞和非阻塞(线程内调用):

阻塞和非阻塞是对同一个线程来说的,在某个时刻,线程要么处于阻塞,要么处于非阻塞

阻塞和非阻塞关注的是程序在等待调用结果(消息、返回值)时的状态:

阻塞调用是指在调用结果返回之前,当前线程会被挂起,调用线程只有在得到结果之后才会返回

非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程

IO分为两个操作:第一步是发起IO请求,第二步真正执行IO读写操作

同步和异步IO的概念:

同步是用户线程发起IO请求之后一直等待或者轮询内核IO操作完成之后才能继续执行后续代码。

异步是用户线程发起IO请求后仍然继续执行后续代码,当内核IO操作完成后会通知用户线程,或者调用用户线程注册的回调函数

阻塞和非阻塞IO的概念:

阻塞是指IO读写操作需要彻底完成后才能返回用户空间

非阻塞是指IO读写操作被调用后立即返回一个状态值,无需等待IO操作彻底完成

四种组合方式:

参考资料:

Java NIO三大角色Channel、Buffer、Selector相关解析 - 掘金 (juejin.cn)icon-default.png?t=N7T8https://juejin.cn/post/7037028848487104519#heading-28Java NIO 中的 Channel 详解 - 掘金 (juejin.cn)icon-default.png?t=N7T8https://juejin.cn/post/7058948260529963039JAVA中BIO、NIO、AIO的分析理解-阿里云开发者社区 (aliyun.com)icon-default.png?t=N7T8https://developer.aliyun.com/article/726698#slide-26Java核心(五)深入理解BIO、NIO、AIO - 腾讯云开发者社区-腾讯云 (tencent.com)icon-default.png?t=N7T8https://cloud.tencent.com/developer/article/1376675Java NIO浅析 - 知乎 (zhihu.com)icon-default.png?t=N7T8https://zhuanlan.zhihu.com/p/23488863彻底理解同步 异步 阻塞 非阻塞 - LittleDonkey - 博客园 (cnblogs.com)icon-default.png?t=N7T8https://www.cnblogs.com/loveer/p/11479249.html

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_56919740/article/details/129966117

智能推荐

python opencv resize函数_python opencv 等比例调整(缩放)图片分辨率大小代码 cv2.resize()...-程序员宅基地

文章浏览阅读1.3k次。# -*- coding: utf-8 -*-"""@File : 200113_等比例调整图像分辨率大小.py@Time : 2020/1/13 13:38@Author : Dontla@Email : [email protected]@Software: PyCharm"""import cv2def img_resize(image):height, width = image...._opencv小图等比例缩放

【OFDM、OOK、PPM、QAM的BER仿真】绘制不同调制方案的误码率曲线研究(Matlab代码实现)-程序员宅基地

文章浏览阅读42次。对于这些调制技术的误码率(BER)研究是非常重要的,因为它们可以帮助我们了解在不同信道条件下系统的性能表现。通过以上步骤,您可以进行OFDM、OOK、PPM和QAM的误码率仿真研究,并绘制它们的误码率曲线,以便更好地了解它们在不同信道条件下的性能特点。针对这些调制技术的BER研究是非常重要的,可以帮助我们更好地了解这些技术在不同信道条件下的性能表现,从而指导系统设计和优化。6. 分析结果:根据误码率曲线的比较,分析每种调制方案在不同信噪比条件下的性能,包括其容忍的信道条件和适用的应用场景。_ber仿真

【已解决】Vue的Element框架,日期组件(el-date-picker)的@change事件,不会触发。_el-date-picker @change不触发-程序员宅基地

文章浏览阅读2.5w次,点赞3次,收藏3次。1、场景照抄官方的实例,绑定了 myData.Age 这个值。实际选择某个日期后,从 vuetool(开发工具)看,值已经更新了,但视图未更新。2、尝试绑定另一个值: myData,可以正常的触发 @change 方法。可能是:值绑定到子对象时,组件没有侦测到。3、解决使用 @blur 代替 @change 方法。再判断下 “值有没有更新” 即可。如有更好的方法,欢迎评论!..._el-date-picker @change不触发

PCL学习:滤波—Projectlnliers投影滤波_projectinliers-程序员宅基地

文章浏览阅读1.5k次,点赞2次,收藏8次。Projectlnliersclass pcl: : Projectlnliers< PointT >类 Projectlnliers 使用一个模型和一组的内点的索引,将内点投影到模型形成新的一个独立点云。关键成员函数 void setModelType(int model) 通过用户给定的参数设置使用的模型类型 ,参数 Model 为模型类型(见 mo..._projectinliers

未处理System.BadImageFormatException”类型的未经处理的异常在 xxxxxxx.exe 中发生_“system.badimageformatexception”类型的未经处理的异常在 未知模块。 -程序员宅基地

文章浏览阅读2.4k次。“System.BadImageFormatException”类型的未经处理的异常在 xxxx.exe 中发生其他信息: 未能加载文件或程序集“xxxxxxx, Version=xxxxxx,xxxxxxx”或它的某一个依赖项。试图加载格式不正确的程序。此原因是由于 ” 目标程序的目标平台与 依赖项的目标编译平台不一致导致,把所有的项目都修改到同一目标平台下(X86、X64或AnyCPU)进行编译,一般即可解决问题“。若果以上方式不能解决,可采用如下方式:右键选择配置管理器,在这里修改平台。_“system.badimageformatexception”类型的未经处理的异常在 未知模块。 中发生

PC移植安卓---2018/04/26_电脑软件移植安卓-程序员宅基地

文章浏览阅读2.4k次。记录一下碰到的问题:1.Assetbundle加载问题: 原PC打包后的AssetBundle导入安卓工程后,加载会出问题。同时工程打包APK时,StreamingAssets中不能有中文。解决方案: (1).加入PinYinConvert类,用于将中文转换为拼音(多音字可能会出错,例如空调转换为KongDiao||阿拉伯数字不支持,如Ⅰ、Ⅱ、Ⅲ、Ⅳ(IIII)、Ⅴ、Ⅵ、Ⅶ、Ⅷ、Ⅸ、Ⅹ..._电脑软件移植安卓

随便推点

聊聊线程之run方法_start 是同步还是异步-程序员宅基地

文章浏览阅读2.4k次。话不多说参考书籍 汪文君补充知识:start是异步,run是同步,start的执行会经过JNI方法然后被任务执行调度器告知给系统内核分配时间片进行创建线程并执行,而直接调用run不经过本地方法就是普通对象执行实例方法。什么是线程?1.现在几乎百分之百的操作系统都支持多任务的执行,对计算机来说每一个人物就是一个进程(Process),在每一个进程内部至少要有一个线程实在运行中,有时线..._start 是同步还是异步

制作非缘勿扰页面特效----JQuery_单击标题“非缘勿扰”,<dd>元素中有id属性的<span>的文本(主演、导演、标签、剧情-程序员宅基地

文章浏览阅读5.3k次,点赞9次,收藏34次。我主要用了层次选择器和属性选择器可以随意选择,方便简单为主大体CSS格式 大家自行构造网页主体<body> <div class='main' > <div class='left'> <img src="images/pic.gif" /> <br/><br/> <img src="images/col.gif" alt="收藏本片"/&_单击标题“非缘勿扰”,元素中有id属性的的文本(主演、导演、标签、剧情

有了这6款浏览器插件,浏览器居然“活了”?!媳妇儿直呼“大开眼界”_浏览器插件助手-程序员宅基地

文章浏览阅读901次,点赞20次,收藏23次。浏览器是每台电脑的必装软件,去浏览器搜索资源和信息已经成为我们的日常,我媳妇儿原本也以为浏览器就是上网冲浪而已,哪有那么强大,但经过我的演示之后她惊呆了,直接给我竖起大拇指道:“原来浏览器还能这么用?大开眼界!今天来给大家介绍几款实用的浏览器插件,学会之后让你的浏览器“活过来”!_浏览器插件助手

NumPy科学数学库_数学中常用的环境有numpy-程序员宅基地

文章浏览阅读101次。NumPy是Python中最常用的科学数学计算库之一,它提供了高效的多维数组对象以及对这些数组进行操作的函数NumPy的核心是ndarray(N-dimensional array)对象,它是一个用于存储同类型数据的多维数组Numpy通常与SciPy(Scientific Python)和 Matplotlib(绘图库)一起使用,用于替代MatLabSciPy是一个开源的Python算法库和数学工具包;Matplotlib是Python语言及其Numpy的可视化操作界面'''_数学中常用的环境有numpy

dind(docker in docker)学习-程序员宅基地

文章浏览阅读1.1w次。docker in docker说白了,就是在docker容器内启动一个docker daemon,对外提供服务。优点在于:镜像和容器都在一个隔离的环境,保持操作者的干净环境。想到了再补充 :)一:低版本启动及访问启动1.12.6-dinddocker run --privileged -d --name mydocker docker:1.12.6-dind在其他容器访问d..._dind

推荐文章

热门文章

相关标签