Java BIO,NIO,AIO

一丶IO模型&Java IO#

Unix为程序员提供了以下5种基本的io模型:

  • blocking io: 阻塞io
  • nonblocking io: 非阻塞io
  • I/O multiplexing: io多路复用
  • signal driven I/O:信号驱动io
  • asynchronous I/O:异步io

但我们平时工作中说的最多是,阻塞非阻塞同步异步

1.阻塞非阻塞,同步异步#

  • 阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。

    image-20230405114445720

    可用把上面的阻塞队列看作是外卖柜

    put方法就是外卖员放外卖,如果容量不够那么一直等待其他用户拿走外卖,这是阻塞。

    offer方法也是外卖员放外卖,但是他发现容量不够的时候,返回false,然后采取其他行动,比如打电话喊你下来拿外卖。

  • 同步与异步关注的是消息通信机制。同步是发起调用在没有得到结果之前,该调用就不返回。异步是发起调用后,这个调用就直接返回了。

    消息队列中间件的作用之一就是异步,发送方将消息发送就立马返回了,不需要等待这个消息被消费者处理。

    同步就是你打电话问外卖员外卖到哪里了,外卖员告知你之前你不挂断电话。

    异步就是你外卖app上发消息问外卖员,发完消息你立马可用做其他的事情。

    异步情况下你怎么知道外卖到哪里了昵?

    • 通知

      外卖员通过平台回复你

    • 回调

      你给外卖员注册了一个回调事件——收到消息后,请回电告知,然后你调用结束,继续处理你的事情,但是外卖员收到消息后,会回调进行电话。

2.Unix的io模型#

io操作分为两步:

  • 等待数据就绪

    例如读文件的过程中需要等待磁盘扫描所需数据,等待数据到达内核缓冲区

  • 将数据从内核空间拷贝到用户空间

    对于一次读取IO的操作,数据并不会直接拷贝到应用程序的缓冲区(用户空间),它首先会被拷贝到操作系统内核的缓冲区(内核空间)中,然后才会从操作系统内核的缓冲区拷贝到应用程序的缓冲区。

2.1 blocking io阻塞io#

img

首先是我们用户进行进行系统调用,产生中断,操作系统切换到内核态,随后是内核完成数据准备和数据从内核空间复制到用户空间,然后应用进程继续运行。

这里说的阻塞,是系统调用不会立即返回,而是需要阻塞知道数据准备完成,并拷贝到用户空间。

2.2 nonblocking io 非阻塞io#

img

可看到,和阻塞io的区别在于,准备数据的这个过程,是应用程序不断进行系统调用,询问操作系统内核是否完成了数据准备,此系统调用不会阻塞直到数据准备完成,而是立马返回。

但是第二阶段,数据从内核空间复制到用户空间是阻塞的,这个过程通常是比较快速的,因为这时候已经有DMA控制器完成了数据从磁盘搬运到内存,只需要拷贝到用户态空间中即可。

2.3 I/O multiplexing io多路复用#

img

可以看到IO多路复用的流程和blocking io阻塞io类似,甚至还会多一次系统调用。那么IO多路复用存在的意义是什么昵?

假设我们当前的进程是一个服务端程序,存在多个网络io需要处理,我们需要多个线程取处理多个网络io,并且多个线程都是阻塞在系统调用上的,这是对线程资源的浪费。

io多路复用的优点就是:可以使用一个线程监听多路io,这个线程阻塞与select系统调用上,当多路io存在任何一个io可读的时候,线程将被唤醒,然后进行数据的拷贝,并进行处理,从而节省线程资源。

2.4 signal driven I/O信号驱动io#

img

可以看到,信号驱动的io在数据准备阶段是非阻塞的,当操作系统完成数据准备后将发送信号来通知用户进程发生了某事件,用户进程需要编写对应的信号处理函数,在信号处理函数中阻塞与内核数据拷贝,待拷贝完成后对数据进行处理。

2.5 asynchronous I/O 异步io#

img

上面四种模型都会在数据从内核空间,拷贝到用户空间这一步发生阻塞,也就是说至少第二步是需要同步等待操作系统完成拷贝的。

异步io模型则解决了这个问题,应用程序只要通知内核要读取的套接字对象, 以及数据的接收地址, 则整个过程都是由内核独立来完成, 包括数据从内核空间向用户空间的拷贝,拷贝完成后再通过信号来通知用户进程。

2.java中的io模型#

阻塞非阻塞同步异步进行组合

  • 阻塞同步io

    这就是java中的BIO

  • 非阻塞同步io

    这就是java中的NIO,java中的nio是通过io多路复用实现的

  • 非阻塞异步io

    这就是java中的AIO,java中的AIO也是通过io多路复用实现,呈现出异步的表象

二丶Java BIO#

下面探讨下java中BIO实现Socket编程方面的不足

public static void main(String[] args) throws IOException {

    ExecutorService threadPool 
            = new ThreadPoolExecutor(10,10,100, TimeUnit.SECONDS,new ArrayBlockingQueue<>(100));

    // 1 创建一个socket server监听tcp 1111端口
    ServerSocket serverSocket = new ServerSocket(1111);
    // 2 阻塞式接受来自客户端的连接
    while (true) {
        //这一步是阻塞的  阻塞直到有客户端连接上来
        Socket socket = serverSocket.accept();
        System.out.println(socket.getRemoteSocketAddress() + "连接到服务端");
        // 3 为了不影响后续连接进来处理,使用多线程来处理连接
        threadPool.execute(() -> process(socket));
    }
}

private static void process(Socket socket) {
    try (OutputStream out = socket.getOutputStream()) {
        byte[] buffer = new byte[1024];
        int len;
        while ((len = socket.getInputStream().read(buffer)) > 0) {
            System.out.println(socket.getRemoteSocketAddress() + "发送数据:" + new String(buffer, 0, len));
            out.write(buffer, 0, len);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

上面代码实现了,如果客户端请求过来讲客户端请求原封不动的写回,可以看到为了实现实现服务端支持多个客户端的连接,我们使用的线程池。

首先Socket socket = serverSocket.accept(),这一步会阻塞直到有客户端连接上来(这一步无所谓,甚至避免了主线程无休止的自旋)

其次process方法中拿到输入输出流写回的操作也是阻塞的,这一步需要使用操作系统提供的系统调用,将数据从网卡或者硬盘读入内核空间,然后从内核空间拷贝到用户空间,我们的java程序才可以进行读操作,写则反之。

由于read,write这两个方法是阻塞式的,它需要阻塞直到系统调用完成,我们的程序傻傻阻塞等待,因此我们使用了线程池,希望一个线程处理一个客户端请求,阻塞也只阻塞线程池中的线程。但是process方法中阻塞的这部分,会体现在我们线程池的线程,也就是说,线程池中存在一些线程阻塞于read,write函数。

这种模型的优点:

  • 简单直接,可以让开发人员专注于编写process的业务逻辑
  • 不用过多考虑系统的过载、限流等问题。线程池本身就是一个天然的漏斗,可以缓冲一些系统处理不了的连接或请求。
  • 使用多线程利用多核心cpu的能力,当线程阻塞的时候,cpu可以切换时间片给其他线程

这种模型的缺点:

  • 非常依赖于线程,线程是宝贵的资源,虽然使用线程池进行了复用,当前当大量请求到来的时候,我们无法无限制的开辟线程。众多的线程被挂起,被唤醒还会导致上下文切换频繁,cpu利用率降低
  • 线程本身占用较大内存,过多的线程导致jvm内存岌岌可危

那么怎么解决上述的问题昵,能不能解放线程不让他们阻塞在read和write中,能读那就读,不能读那就继续处理其他socket?

三丶Java NIO#

image-20230405164149306

回顾这张图,我们上面说的解放线程不让他们阻塞在read和write中,能读那就读,不能读那就继续处理其他socket,不正是上面非阻塞的方式,希望系统调用可以立即返回,而不是阻塞。

Java中的nio基于io多路复用实现了同步非阻塞的处理方式

public static void main(String[] args) throws IOException, InterruptedException {
    // 1 创建selector用来侦听多路IO消息 '文件描述符'
    // selector 担任了重要的通知角色,可以将任意IO注册到selector上,通过非阻塞轮巡selector来得知哪些路IO有消息了
    // 底层是epoll(linux下)
    // 后续会把server端注册上来,有服务端被客户端连接上来的IO消息
    // 也会把每个客户端连接注册上来,有客户端发送过来的数据
    Selector selector = Selector.open();
    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

    // 2 把server端注册上去
    ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
    serverSocketChannel.socket().bind(new InetSocketAddress("127.0.0.1", 1111));
    //配置为非阻塞
    serverSocketChannel.configureBlocking(false);
    //关心accept事件,
    serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
    while (true) {
        // 3 这一步是阻塞的,基于io多路复用中的select poll,epoll
        // 这里可以设置等待事件
        if (selector.select() == 0) {
            continue;
        }

        // 4 如果有至少一路IO有消息,那么set不为空
        Set<SelectionKey> selectionKeys = selector.selectedKeys();
        for (SelectionKey key : selectionKeys) {
            if (key.isAcceptable()) {
                System.out.println("客户端连接");
                // 因为我们只注册了serverSocketChannel这一个可以accept的所以这里用强转即可
                SocketChannel socketChannel = ((ServerSocketChannel) key.channel()).accept();
                socketChannel.configureBlocking(false);
                // 5 当第一次客户端连接时,就将这个连接也作为channel注册上,他是可读型的
                //当前只是有客户端连接上来了,但是并不代表可读,还需要DMA将网卡数据搬运到内存
                socketChannel.register(selector, SelectionKey.OP_READ);
            } else if (key.isReadable()) {
                // 6 因为步骤5把客户端连接也注册上来了,并且是可读上面的数据的,如果该channel被选出来说明有客户端数据来了
                SocketChannel socketChannel = (SocketChannel) key.channel();
                // 7 必须借助ByteBuffer接受和发送数据
                byteBuffer.clear();
                if (socketChannel.read(byteBuffer) <= 0) {
                    continue;
                }
                byteBuffer.flip();
                byte[] b = new byte[byteBuffer.limit()];
                byteBuffer.get(b);
                System.out.println(key + " 数据来了: " + new String(b));
                byteBuffer.clear();
                byteBuffer.put(b);
                byteBuffer.flip();
                socketChannel.write(byteBuffer);
            }
        }
        // 8 非常重要一定要清理掉每个channel的key,来表示已经处理过了,不然下一次还会被select
        selectionKeys.clear();
    }
}

select是阻塞的,无论是通过操作系统的通知(epoll)还是不停的轮询(select,poll),这个函数是阻塞的,它还支持超时阻塞模式。这是一个线程监听多路io的体现,只要有一个事件就绪那么select就会返回。

socketChannel.configureBlocking(false)将 socketChannel设置为非阻塞其读写操作都是非阻塞的,也就说如果无法读,那么read函数返回-1,将会让当前线程去遍历其他就绪的事件,而不是傻傻等待,这是非阻塞io的体现

四丶Java AIO#

 public static void main(String[] args) throws IOException, InterruptedException {
        AsynchronousServerSocketChannel serverChannel =
                AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(1111));
        System.out.println(Thread.currentThread() + "开始监听1111端口");
        serverChannel.accept(null, new CompletionHandler<>() {
            @SneakyThrows
            @Override
            public void completed(AsynchronousSocketChannel channel, Object attachment) {
                // 递归注册accept
                serverChannel.accept(attachment, this);
                System.out.println(Thread.currentThread() + "有客户端连接上来了" + channel.getRemoteAddress());
                ByteBuffer buffer = ByteBuffer.allocate(1024);
                channel.read(buffer, null, new CompletionHandler<Integer, ByteBuffer>() {
                    @SneakyThrows
                    @Override
                    public void completed(Integer len, ByteBuffer attachment) {
                        // 递归注册read
                        channel.read(buffer, null, this);
                        buffer.flip();
                        System.out.println(channel.getRemoteAddress() + ":" + new String(buffer.array(), 0, len));
                        buffer.clear();
                        channel.write(ByteBuffer.wrap("HelloClient".getBytes()));
                    }

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

                    }
                });
            }

            @Override
            public void failed(Throwable exc, Object attachment) {
            }
        });
        Thread.sleep(Integer.MAX_VALUE);
    }

AIO中,所有创建的通道都会直接在OS上注册监听,当出现IO请求时,会先由操作系统接收、准备、拷贝好数据,然后再通知监听对应通道的程序处理数据。

客户端的连接到来后同样会先注册到选择器上,但客户端的I/O请求会先交由OS处理,当内核将数据拷贝完成后才会分配一条线程处理。这一点不同于BIO和NIO,NIO和BIO在内核拷贝数据到用户态的这一步任然是阻塞的。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/48132.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

理解跨平台技术

1、为什么需要跨平台技术 write once&#xff0c;run everywhere 开发一个APP运行在Android手机需要一套代码&#xff0c;运行在ios操作系统的手机又需要一套代码&#xff0c;为了使同一套代码能运行在不同的操作系统上&#xff0c;解决多端独立开发的问题&#xff0c;跨平台…

综合案例(面向对象)

使用面向对象思想完成数据读取和处理基于面向对象思想重新认知第三方库使用&#xff08;PyEcharts&#xff09; 数据分析案例 某公司&#xff0c;有2份数据文件&#xff0c;现需要对其进行分析处理&#xff0c;计算每日的销售额并以柱状图表的形式进行展示。 数据内容 综合案…

分享VMware Workstation Pro ESXI7创建虚拟机和配置硬盘空间(分享自己的学习历程意在帮助有需要的小伙伴)

背景&#xff1a;因公司项目需求改用VMware Workstation Pro&#xff0c;已经使用1个月目前除了中途出现过一次问题被解决后一直稳定运行至今&#xff0c; 1:这里贴出拿出现的问题提示及解决方法的链接&#xff1a;解决vmWare ESXI 7.3报错; 2:如果你是第一次接触VMware Work…

STM32CubeMX配置STM32G031多通道ADC + DMA采集(HAL库开发)

时钟配置HSI主频配置64M 勾选打开8个通道的ADC 使能连续转换模式 添加DMA DMA模式选择循环模式 使能DMA连续请求 采样时间配置160.5 转换次数为8 配置好8次转换的顺序 配置好串口&#xff0c;选择异步模式配置好需要的开发环境并获取代码 修改main.c 串口重定向 #include &…

基于因果关系知识库的因果事件图谱构建、文本预处理、因果事件抽取、事件融合等

项目设计集合&#xff08;人工智能方向&#xff09;&#xff1a;助力新人快速实战掌握技能、自主完成项目设计升级&#xff0c;提升自身的硬实力&#xff08;不仅限NLP、知识图谱、计算机视觉等领域&#xff09;&#xff1a;汇总有意义的项目设计集合&#xff0c;助力新人快速实…

软件外包开发的流程图工具

软件开发过程中需要画流程图&#xff0c;可以更清楚的表达软件业务流程&#xff0c;减少在开发过程中的业务理解偏差&#xff0c;因此在软件开发过程中流程图工具是必不可少的软件管理工具。今天和大家分享常见的一些软件流程图工具&#xff0c;每款工具都有其自身的特色&#…

uniapp 微信小程序:页面+组件的生命周期顺序

uniapp 微信小程序&#xff1a;页面组件的生命周期顺序 首页页面父组件子组件完整顺序参考资料 这个uniapp的微信小程序项目使用的是 VUE2 首页 首页只提供了一个跳转按钮。 <template><view><navigator url"/pages/myPage/myPage?namejerry" hov…

flask中的session介绍

flask中的session介绍 在Flask中&#xff0c;session是一个用于存储特定用户会话数据的字典对象。它在不同请求之间保存数据。它通过在客户端设置一个签名的cookie&#xff0c;将所有的会话数据存储在客户端。以下是如何在Flask应用中使用session的基本步骤&#xff1a; 首先…

Linux内核中的链表、红黑树和KFIFO

lLinux内核代码中广泛使用了链表、红黑树和KFIFO。 一、 链表 linux内核代码大量使用了链表这种数据结构。链表是在解决数组不能动态扩展这个缺陷而产生的一种数据结构。链表所包含的元素可以动态创建并插入和删除。链表的每个元素都是离散存放的&#xff0c;因此不需要占用连…

kafka消息监听

1&#xff0c;spring配置kafka网址 2&#xff0c;listener Component public class OrderMsgListener {KafkaListener(topics "order",groupId "order-service")public void listen(ConsumerRecord record){System.out.println("收到消息&#xf…

IPv6 over IPv4

IPv6 over IPv4隧道简介 IPv6 over IPv4隧道可实现IPv6网络孤岛之间通过IPv4网络互连。由于IPv4地址的枯竭和IPv6的先进性&#xff0c;IPv4过渡为IPv6势在必行。因为IPv6与IPv4的不兼容性&#xff0c;所以需要对原有的IPv4设备进行替换。但是如果贸然将IPv4设备大量替换所需成…

11.python设计模式【责任链模式】

内容&#xff1a;使多个对象都有机会处理请求&#xff0c;从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链&#xff0c;并沿着这条链传递该请求&#xff0c;直到有一个对象处理它为止。角色&#xff1a; 抽象处理者&#xff08;Handler&#xff09;具体处理…

【用户体验分析报告】 按需加载组件,导致组件渲染卡顿,影响交互体验?组件拆包预加载方案来了!

首先&#xff0c;我们看一些针对《如何提升应用首屏加载体验》的文章&#xff0c;提到的必不可少的措施&#xff0c;便是减少首屏幕加载资源的大小&#xff0c;而减少资源大小必然会想到按需加载措施。本文提到的便是一个基于webpack 插件与 react 组件实现的一套研发高度自定义…

索马里ECTN认证开船后办?都可以办的,

索马里ECTN认证开船后办&#xff1f;都可以办的&#xff0c;没有特别时间要求&#xff0c;可以在开船前办&#xff0c;也可以在开船后再办。因为索马里ECTN货物跟踪单看上去像是一份“证书”的文件&#xff0c;主要作用是用于目的港清关&#xff0c;所以很多客户习惯把它称为EC…

50条必背JAVA知识点(三)

31.面向对象中两个重要的概念&#xff1a;类&#xff1a;对一类事物的描述&#xff0c;是抽象的、概念上的定义对象&#xff1a;是实际存在的该类事物的每个个体&#xff0c;因而也称为实例(instance) 32.虚拟机栈&#xff0c;即为平时提到的栈结构。局部变量存储在栈结构中&am…

【数据动态填充到element表格;将带有标签的数据展示为文本格式】

一&#xff1a;数据动态填充到element表格&#xff1b; 二&#xff1a;将带有标签的数据展示为文本格式&#xff1b; 1、 <el-row><el-col :span"24"><el-tabs type"border-card"><el-tab-pane label"返回值"><el-…

IL汇编字符串连接

在此实现了一个基本的IL汇编程序&#xff1b; 了解MSIL汇编和IL汇编评估堆栈_bcbobo21cn的博客-CSDN博客 它用了下面两句来在屏幕输出字符串&#xff0c; ldstr "I am from the IL Assembly Language..." call void [mscorlib]System.Console::WriteLine (string) …

WMS是什么意思,WMS有什么功能

阅读本篇文章&#xff0c;您可以了解到&#xff1a;1、WMS的定义&#xff1b;2、WMS的功能&#xff1b;3、WMS的好处&#xff1b;4、WMS的未来。 一、WMS的定义 WMS全称为Warehouse Management System&#xff0c;即仓库管理系统&#xff0c;是一种用于管理和控制仓库操作的软…

day42-Live User Filter(实时用户过滤器)

50 天学习 50 个项目 - HTMLCSS and JavaScript day42-Live User Filter&#xff08;实时用户过滤器&#xff09; 效果 index.html <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewport…

CTF PWN-攻防世界CGfsb格式化字符串漏洞

文章目录 前言格式化字符串漏洞格式化字符串漏洞基本原理简单典型案例 漏洞的危害与利用拒绝服务攻击内存数据读取内存数据覆盖 攻防世界&#xff1a;CGfsg题目思路简析任意地址覆写 总结 前言 距离 2021 年年底短暂接触学习 CTF PWN 相关知识&#xff08;CTF PWN-攻防世界XCT…