Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Java 中 3 种常见的 IO 模型

Java 中 3 种常见的 IO 模型

作者头像
wsuo
发布于 2021-06-24 02:41:59
发布于 2021-06-24 02:41:59
30100
代码可运行
举报
文章被收录于专栏:技术进阶之路技术进阶之路
运行总次数:0
代码可运行

知识背景

操作系统:

  • 为了保证操作系统的稳定性和安全性,一个进程的地址空间被分为 用户空间内核空间
  • 用户空间不能直接访问内核空间,要想访问必须进行 系统调用
  • IO 操作只有内核空间才能完成,所以用户进程需要进行系统调用;
  • 所以用户空间仅仅是发起系统调用请求,真正的 IO 操作执行是由内核空间完成的。

常见的 IO 模型:

  • 同步阻塞 IO ⭐
  • 同步非阻塞 IO
  • IO 多路复用 ⭐
  • 信号驱动 IO
  • 异步 IO ⭐

其中带有星号的模型为 java 中常见的 3 种模型,下面将分别介绍。

BIO

BIO 即 Blocking I/O;字面意思就可以看出它属于同步阻塞 IO。

如下图,应用程序发出一个 read 调用,内核空间需要经历准备数据的几个阶段,准备好之后返回数据给应用程序。期间如果另一个应用程序也需要 read 调用,那么它必须等待;这就是阻塞。

BIO 最大的特点就是一次只能处理一个调用,这在高并发的场景下肯定是不行的。

示例代码

客户端:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class IOClient {

  public static void main(String[] args) {
    // 创建多个线程,模拟多个客户端连接服务端
    new Thread(() -> {
      try {
        Socket socket = new Socket("127.0.0.1", 3333);
        while (true) {
          try {
            socket.getOutputStream().write((new Date() + ": hello world").getBytes());
            Thread.sleep(2000);
          } catch (Exception e) {
          }
        }
      } catch (IOException e) {
      }
    }).start();
  }
}

服务端:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class IOServer {

  public static void main(String[] args) throws IOException {
    // TODO 服务端处理客户端连接请求
    ServerSocket serverSocket = new ServerSocket(3333);

    // 接收到客户端连接请求之后为每个客户端创建一个新的线程进行链路处理
    new Thread(() -> {
      while (true) {
        try {
          // 阻塞方法获取新的连接
          Socket socket = serverSocket.accept();
          
          // 每一个新的连接都创建一个线程,负责读取数据
          new Thread(() -> {
            try {
              int len;
              byte[] data = new byte[1024];
              InputStream inputStream = socket.getInputStream();
              // 按字节流方式读取数据
              while ((len = inputStream.read(data)) != -1) {
                System.out.println(new String(data, 0, len));
              }
            } catch (IOException e) {
            }
          }).start();
        } catch (IOException e) {
        }
      }
    }).start();
  }
}

NIO

NIO 就是 Non-blocking I/O。字面翻译为非阻塞,但其实它是属于多路复用模型。下面就非阻塞模型和多路复用模型作简要区分:

  • 非阻塞模型:关键词是 轮询 ,例如小明需要找人帮忙,于是找到张三,第一次张三在忙,第二次张三还在忙,此后小明的做法是每一个小时来一次,直到等到张三有空为止。该做法很不明智,具体体现在浪费了小明的时间,来来回回都是需要消耗处理器资源的。
  • 多路复用模型:还是小明需要帮忙,不过这次多了一个查询系统,这个系统可以提供谁有空,小明经过查询发现 3 个好朋友当中只有李四有空,于是找了李四帮忙。这就避免了浪费处理器资源。

如图,在多路复用模型中,线程想获得内核空间的数据,必须先发起 select 系统调用来询问内核空间是否有空;当内核空间有空时会回复应用程序一个 ready 。应用程序得知内核空间准备就绪之后就会再次发送 read 调用来请求数据。

  • select 系统调用:内核提供的系统调用,它支持一次查询多个系统调用的可用状态。几乎所有的操作系统都支持。
  • 这里的 select 调用相当于上例中的查询系统;ready 相当于查到了李四有空。

IO 多路复用模型最大的特点是通过减少无效的系统调用,减少了对 CPU 资源的消耗。

Java 中的 NIO 于 Java 1.4 中引入,对应 java.nio 包,提供了 Channel , SelectorBuffer 等抽象。其中有一个非常重要的选择器 ( Selector ) 的概念,也可以被称为 多路复用器。通过它,只需要一个线程(单线程)便可以管理多个客户端连接。只有当客户端数据到了之后,才会为其服务。

这里 Selector 选择器的作用是监听多个通道的状态,判断是否空闲。

使用单线程管理的理由是,从操作系统的角度来看,切换线程开销是比较昂贵的,并且每个线程都需要占用系统资源,因此暂用线程越少越好。

示例代码

客户端:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class IOClient {

  public static void main(String[] args) {
    // 创建多个线程,模拟多个客户端连接服务端
    new Thread(() -> {
      try {
        Socket socket = new Socket("127.0.0.1", 3333);
        while (true) {
          try {
            socket.getOutputStream().write((new Date() + ": hello world").getBytes());
            Thread.sleep(2000);
          } catch (Exception e) {
          }
        }
      } catch (IOException e) {
      }
    }).start();
  }
}

服务端:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class NIOServer {
  public static void main(String[] args) throws IOException {
    // 1. serverSelector负责轮询是否有新的连接,服务端监测到新的连接之后,不再创建一个新的线程,
    // 而是直接将新连接绑定到clientSelector上,这样就不用 IO 模型中 1w 个 while 循环在死等
    Selector serverSelector = Selector.open();
    // 2. clientSelector负责轮询连接是否有数据可读
    Selector clientSelector = Selector.open();

    new Thread(() -> {
      try {
        // 对应IO编程中服务端启动
        ServerSocketChannel listenerChannel = ServerSocketChannel.open();
        listenerChannel.socket().bind(new InetSocketAddress(3333));
        listenerChannel.configureBlocking(false);
        listenerChannel.register(serverSelector, SelectionKey.OP_ACCEPT);

        while (true) {
          // 监测是否有新的连接,这里的1指的是阻塞的时间为 1ms
          if (serverSelector.select(1) > 0) {
            Set<SelectionKey> set = serverSelector.selectedKeys();
            Iterator<SelectionKey> keyIterator = set.iterator();

            while (keyIterator.hasNext()) {
              SelectionKey key = keyIterator.next();

              if (key.isAcceptable()) {
                try {
                  // (1) 每来一个新连接,不需要创建一个线程,而是直接注册到clientSelector
                  SocketChannel clientChannel = ((ServerSocketChannel) key.channel()).accept();
                  clientChannel.configureBlocking(false);
                  clientChannel.register(clientSelector, SelectionKey.OP_READ);
                } finally {
                  keyIterator.remove();
                }
              }
            }
          }
        }
      } catch (IOException ignored) {
      }
    }).start();
    new Thread(() -> {
      try {
        while (true) {
          // (2) 批量轮询是否有哪些连接有数据可读,这里的1指的是阻塞的时间为 1ms
          if (clientSelector.select(1) > 0) {
            Set<SelectionKey> set = clientSelector.selectedKeys();
            Iterator<SelectionKey> keyIterator = set.iterator();

            while (keyIterator.hasNext()) {
              SelectionKey key = keyIterator.next();

              if (key.isReadable()) {
                try {
                  SocketChannel clientChannel = (SocketChannel) key.channel();
                  ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                  // (3) 面向 Buffer
                  clientChannel.read(byteBuffer);
                  byteBuffer.flip();
                  System.out.println(
                      Charset.defaultCharset().newDecoder().decode(byteBuffer).toString());
                } finally {
                  keyIterator.remove();
                  key.interestOps(SelectionKey.OP_READ);
                }
              }
            }
          }
        }
      } catch (IOException ignored) {
      }
    }).start();
  }
}

AIO

AIO 即 Asynchronous I/O,也可以称之为 NIO 2。Java 7 中引入,它是异步 IO 模型。

异步 IO 是基于事件和回调机制实现的,也就是说应用请求之后会直接返回,不会阻塞在那里,当后台处理完成,操作系统会通知响应的线程进行后续的操作。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021/06/22 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
彻底搞懂NIO效率高的原理
这篇文章读不懂的没关系,可以先收藏一下。笔者准备介绍完epoll和NIO等知识点,然后写一篇Java网络IO模型的介绍,这样可以使Java网络IO的知识体系更加地完整和严谨。初学者也可以等看完IO模型介绍的博客之后,再回头看这些博客,会更加有收获。
全菜工程师小辉
2019/08/16
2.6K0
NIO 读数据和写数据方式
整个 NIO 体系包含的类远远不止这三个,只能说这三个是 NIO 体系的 “核心 API”。上面已经对这三个概念进行了基本的阐述,这里就不多做解释了。
happyJared
2019/08/08
7330
NIO (New I/O)
NIO是一种同步非阻塞的I/O模型,在Java 1.4 中引入了 NIO 框架,对应 java.nio 包,提供了 Channel , Selector,Buffer等抽象。
崔笑颜
2020/06/08
8140
Java面试常考的 BIO,NIO,AIO 总结
熟练掌握 BIO,NIO,AIO 的基本概念以及一些常见问题是你准备面试的过程中不可或缺的一部分,另外这些知识点也是你学习 Netty 的基础。
Java技术江湖
2019/09/25
8060
Java面试常考的 BIO,NIO,AIO 总结
一篇文章搞定Netty入门
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
用户1212940
2019/11/13
4230
【Netty】NIO编程的利器
今天换换口味,由于本人工作中马上要用到Netty这个东西,所以这几天也是开始学习,此学习过程应该会是一个完整的系列,初步的目标是先会用,之后有机会再深入。鉴于笔者之前也从未使用过Netty,所以有什么疏漏错误的,希望大家指正,先行感谢!
周三不加班
2019/09/04
4140
【Netty】NIO编程的利器
为什么一个还没毕业的大学生能够把 IO 讲的这么好?
Java IO 是一个庞大的知识体系,很多人学着学着就会学懵了,包括我在内也是如此,所以本文将会从 Java 的 BIO 开始,一步一步深入学习,引出 JDK1.4 之后出现的 NIO 技术,对比 NIO 与 BIO 的区别,然后对 NIO 中重要的三个组成部分进行讲解(缓冲区、通道、选择器),最后实现一个简易的客户端与服务器通信功能。
cxuan
2020/09/14
6150
BIO、NIO、IO多路复用模型的演进&Java NIO 网络编程
上文介绍了网络编程的基础知识,并基于 Java 编写了 BIO 的网络编程。我们知道 BIO 模型是存在巨大问题的,比如 C10K 问题,其本质就是因其阻塞原因,导致如果想要承受更多的请求就必须有足够多的线程,但是足够多的线程会带来内存占用问题、CPU上下文切换带来的性能问题,从而造成服务端崩溃的现象。怎么解决这一问题呢?优化呗,所以后面就有了NIO、AIO、IO多路复用。本文将对这几个模型详细说明并基于 Java 编写 NIO。
王二蛋
2024/01/18
7810
最强开源网络应用框架 Netty,没有之一,直接“榨干”CPU!
👆点击“博文视点Broadview”,获取更多书讯 在开始了解Netty是什么之前,我们先来回顾一下,如果需要实现一个客户端与服务端通信的程序,使用传统的IO编程,应该如何来实现? IO编程 我们简化一下场景:客户端每隔两秒发送一个带有时间戳的“hello world”给服务端,服务端收到之后打印它。 在传统的IO模型中,每个连接创建成功之后都需要由一个线程来维护,每个线程都包含一个while死循环,那么1万个连接对应1万个线程,继而有1万个while死循环,这就带来如下几个问题。 线程资源受限:线程是
博文视点Broadview
2022/03/10
6310
《跟闪电侠学Netty》阅读笔记 - 开篇入门Netty
《跟闪电侠学Netty》 并不是个人接触的第一本Netty书籍,但个人更推荐读者把它作为作为第一本Netty入门的书籍。
阿东
2023/06/27
5120
《跟闪电侠学Netty》阅读笔记 - 开篇入门Netty
从理论到实践:深度解读BIO、NIO、AIO的优缺点及使用场景
BIO、NIO和AIO是Java编程语言中用于处理输入输出(IO)操作的三种不同的机制,它们分别代表同步阻塞I/O,同步非阻塞I/O和异步非阻塞I/O。
索码理
2023/10/10
10.5K0
从理论到实践:深度解读BIO、NIO、AIO的优缺点及使用场景
Java IO 与 NIO:高效的输入输出操作探究
输入输出(IO)是任何编程语言中的核心概念,而在Java中,IO操作更是应用程序成功运行的基石。随着计算机系统变得越来越复杂,对IO的要求也日益增加。在本文中,我们将探讨Java IO和非阻塞IO(NIO)的重要性以及如何在Java中实现高效的输入输出操作。
程序那些事
2023/10/17
2590
BIO与NIO与多路复用
首先需要了解下什么是IO,IO就是读入/写出数据的过程,和等待读入/写出数据的过程。
Lvshen
2022/05/05
3160
BIO与NIO与多路复用
【015期】JavaSE面试题(十五):网络IO流
大家好,我是Java面试题库的提裤姐,今天这篇是JavaSE系列的第十五篇,主要总结了Java中的IO流的问题,IO流分为两篇来讲,这篇是第二篇,主要是网络IO流,在后续,会沿着第一篇开篇的知识线路一直总结下去,做到日更!如果我能做到百日百更,希望你也可以跟着百日百刷,一百天养成一个好习惯。
java进阶架构师
2020/08/28
3530
【015期】JavaSE面试题(十五):网络IO流
从操作系统层面理解Linux下的网络IO模型
从操作系统层面怎么理解网络I/O呢?计算机的世界有一套自己定义的概念。如果不明白这些概念,就无法真正明白技术的设计思路和本质。所以在我看来,这些概念是了解技术和计算机世界的基础。
宜信技术学院
2019/12/20
2.8K0
从操作系统层面理解Linux下的网络IO模型
网络IO
操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的所有权限。为了保证用户进程不能直接操作内核(kernel),保证内核的安全,操心系统将虚拟空间划分为两部分,一部分为内核空间,一部分为用户空间。
ruochen
2021/11/25
4860
java的IO模型
本文主要是重新梳理了Java的IO模型,基于之前NIO的文章进行补充,为学习Netty做准备。
贪挽懒月
2020/07/14
7360
从入门到精通IO模型:长连接、短连接与Java中的IO模型详解
在网络编程中,IO(输入/输出)模型的选择对于系统的性能和稳定性至关重要。随着互联网的发展,终端设备数量的激增对服务器的并发处理能力提出了更高要求。本文将详细介绍IO模型的历史背景、业务场景、功能点以及底层原理,并通过Java代码示例展示不同IO模型的具体用法。内容将涵盖长连接与短连接、有状态与无状态的概念,以及OIO、BIO、NIO、AIO、DIO等IO模型。
小马哥学JAVA
2025/01/05
1480
IO通信模型(三)多路复用IO
从非阻塞同步IO的介绍中可以发现,为每一个接入创建一个线程在请求很多的情况下不那么适用了,因为这会渐渐耗尽服务器的资源,人们也都意识到了这个 问题,因此终于有人发明了IO多路复用。最大的特点就是不需要开那么多的线程和进程。 多路复用IO是指使用一个线程来检查多个文件描述符(Socket)的就绪状态,比如调用select和poll函数,传入多个文件描述符,如果有一个文件描述符就绪,则返回,否则阻塞直到超时。得到就绪状态后进行真正的操作可以在同一个线程里执行,也可以启动线程执行(比如使用线程池)。
未读代码
2019/11/04
4400
IO通信模型(三)多路复用IO
Nginx_BIO_NIO_AIO面试题(2021最新版)
在所有互联网公司中,Nginx 作为最常用的 7 层负载均衡代理层,每个后端开发人员和运维人员都应该对其有较为深入的理解。
Java程序猿
2021/04/27
2.8K0
相关推荐
彻底搞懂NIO效率高的原理
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验