Java I/O模型及其底层原理

简介:

Java I/O模型及其底层原理

Java I/O是Java基础之一,在面试中也比较常见,在这里我们尝试通过这篇文章阐述Java I/O的基础概念,帮助大家更好的理解Java I/O。
在刚开始学习Java I/O时,我很迷惑,因为网上绝大多数的文章都是讲解Linux网络I/O模型的,那时我总是搞不明白和Java I/O的关系。后来查了看了好多,才明白Java I/O的原理是以Linux网络I/O模型为基础的,理解了Linux网络I/O模型再学习Java I/O就很方便了,所以这篇文章,我们先来了解I/O的基本概念,再学习Linux网络I/O模型,最后再看Java中的几种I/O。

什么是I/O?
I/O是Input、Output的缩写,即对应计算机中的输入输出,以一次文件读取为例,我们需要将磁盘上的数据读取到用户空间,那么这次数据转移操作其实就是一次I/O操作,更具体的说是一次文件I/O。我们浏览网页,其中在请求一个网页时,服务器通过网络把数据发送给我们,此时程序将数据从TCP缓冲区复制到用户空间,那么这次数据转移操作其实也是一次I/O操作,更具体的说是一次网络I/O。I/O到处都在,十分重要,Java对I/O对底层操作系统的各种I/O模型进行了封装,使我们可以轻松开发。

Linux网络I/O模型
根据UNIX网络编程对I/O模型的分类,UNIX提供了5种I/O模型,分别是:阻塞I/O(Blocking I/O)、非阻塞I/O(Non-Blacking I/O)、I/O多路复用模型(I/O Multiplexing)、信号驱动式I/O(Signal Driven I/O)、异步I/O(Asynchronous I/O)。我们逐步了解一下其基本原理。

阻塞I/O(Blocking I/O)
阻塞I/O是最早最基础的I/O模型,其在读写数据过程中会阻塞。通过下图我们可以看到,当用户进程调用了recvfrom这个系统调用后,内核开始第一阶段的数据准备工作,直到内核等待数据准备完成,然后开始第二阶段的将数据从内核复制到用户空间的工作,最后内核返回结果。整个过程中用户进程都是阻塞的,直到最后返回结果后才接触阻塞block状态。阻塞I/O模型适用于并发量小且对时延不敏感的系统。

非阻塞I/O(Non-Blacking I/O)
当用户进程调用recvfrom这个系统调用后,如果内核尚未准备好数据,此时不再阻塞用户进程,而是立即返回一个EWOULDBLOCK错误。用户进程会不断发起系统调用直到内核中数据被准备好(轮询),此时将执行第二阶段的将数据从内核复制到用户空间的工作,然后内核返回结果。非阻塞I/O模型不断地轮询往往需要耗费大量cpu时间。

I/O多路复用模型(I/O Multiplexing)
I/O多路复用的优点在于单个进程可以同时处理多个网络连接的I/O,其基本原理就是select/epoll函数可以不断的轮询其负责的所有socket,当某个socket有数据到达时,就通知用户进程。
如下图所示,当用户进程调用select函数时,整个进程会被阻塞block住,但是这里的阻塞不是被socket I/O阻塞,而是被select这个函数阻塞。同时内核会监听改select负责的所有socket(这里的socket一般设置为non-blocking),当任何一个socket中的数据准备好时,select就会返回给用户进程,这时候用户进程再此发起一个系统调用,将数据从内核复制到用户空间,并返回结果。
对比I/O多路复用模型和阻塞I/O模型的流程,多路复用多了一个系统调用来完成select环节,除此之外没有太大的不同。Select的优势在于它可以同时处理多个connection,但是会多一个系统调用。多路复用本质上也不是非阻塞的。

信号驱动式I/O(Signal Driven I/O)
首先我们开启socket的信号驱动I/O功能,然后用户进程发起sigaction系统调用给内核后立即返回并可继续处理其他工作。收到sigaction系统调用的内核在将数据准备好后会按照要求产生一个signo信号通知给用户进程。然后用户进程再发起recvfrom系统调用,完成数据从内核到用户空间的复制,并返回最终结果。其基础原理图示如下:

异步I/O(Asynchronous I/O)
用户进程向内核发起系统调用后,就可以开始去做其他事情了。内核收到异步I/O的系统调用后,会直接retrun,所以这里不会对用户进程有阻塞。之后内核等待数据准备完成后会继续将数据从内核拷贝到用户空间(具体动作可以由异步I/O调用定义),然后内核回给用户进程发送一个signal,告诉用户进程I/O操作完成了,整个过程不会导致用户请求进程阻塞。
信号驱动I/O模型是内核通知我们可以发起I/O操作了,而异步I/O模式是内核告诉我们I/O操作已经完成了。

以上就是Linux的5种网络I/O模型,其中前4中都是同步I/O模型,他们真正的I/O操作环节都会将进程阻塞,只有最后一种异步I/O模型是异步I/O操作。

Java中的I/O模型
在JDK1.4之前,基于Java的所有socket通信都是使用阻塞I/O(BIO),JDK1.4提供了了非阻塞I/O(NIO)功能,不过虽然名字叫做NIO,实际底层模型是I/O多路复用,JDK1.7提供了针对异步I/O(AIO)功能。

BIO
BIO简化了上层开发,但是性能瓶颈问题严重,对高并发第时延支持差。
基于消息队列和线程池技术优化的BIO模式虽然可以对高并发支持有一定帮助,但是还是受限于线程池大小和线程池阻塞队列大小的制约,当并发数超过线程池的处理能力时,部分请求法务继续处理,会导致客户端连接超时,影响用户体验。

NIO
NIO弥补了BIO的不足,简单说就是通过selector不断轮询注册在自己上面的channel,如果channel上面有新的连接读写时间时就会被轮询出来,一个selector上面可以注册多个channel,一个线程就可以负责selector的轮询,这样就可以支持成千上万的连接。Selector就是一个轮询器,channel是一个通道,通过它来读取或者写入数据,通道是双向的,可以用于读、写、读和写。Buffer用来和channel交互,数据通过channel进出buffer。
NIO的优点是可以可靠性好以及高并发低时延,但是使用NIO的代码开发较为复杂。

AIO
AIO,或者说叫做NIO2.0,引入了异步channel的概念,提供了异步文件channel和异步socket channel的实现,开发者可以通过Future类来表示异步操作的结果,也可以在执行异步操作时传入一个channels,实现CompletionHandler接口作为回调。AIO不用开发者单独开发独立线程的selector,异步回调操作有JDK地城思安城池负责驱动,开发起来比NIO简单一些,同时保持了高可靠高并发低时延的优点。

参考:
https://blog.csdn.net/historyasamirror/article/details/5778378
https://juejin.im/post/5cce5019e51d453a506b0ebf

原文地址https://www.cnblogs.com/guodongdidi/p/13085474.html

相关文章
|
22天前
|
存储 Java 关系型数据库
高效连接之道:Java连接池原理与最佳实践
在Java开发中,数据库连接是应用与数据交互的关键环节。频繁创建和关闭连接会消耗大量资源,导致性能瓶颈。为此,Java连接池技术通过复用连接,实现高效、稳定的数据库连接管理。本文通过案例分析,深入探讨Java连接池的原理与最佳实践,包括连接池的基本操作、配置和使用方法,以及在电商应用中的具体应用示例。
41 5
|
12天前
|
存储 算法 Java
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
本文详解自旋锁的概念、优缺点、使用场景及Java实现。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
|
12天前
|
Java
Java之CountDownLatch原理浅析
本文介绍了Java并发工具类`CountDownLatch`的使用方法、原理及其与`Thread.join()`的区别。`CountDownLatch`通过构造函数接收一个整数参数作为计数器,调用`countDown`方法减少计数,`await`方法会阻塞当前线程,直到计数为零。文章还详细解析了其内部机制,包括初始化、`countDown`和`await`方法的工作原理,并给出了一个游戏加载场景的示例代码。
Java之CountDownLatch原理浅析
|
14天前
|
Java 索引 容器
Java ArrayList扩容的原理
Java 的 `ArrayList` 是基于数组实现的动态集合。初始时,`ArrayList` 底层创建一个空数组 `elementData`,并设置 `size` 为 0。当首次添加元素时,会调用 `grow` 方法将数组扩容至默认容量 10。之后每次添加元素时,如果当前数组已满,则会再次调用 `grow` 方法进行扩容。扩容规则为:首次扩容至 10,后续扩容至原数组长度的 1.5 倍或根据实际需求扩容。例如,当需要一次性添加 100 个元素时,会直接扩容至 110 而不是 15。
Java ArrayList扩容的原理
|
17天前
|
存储 缓存 Oracle
Java I/O流面试之道
NIO的出现在于提高IO的速度,它相比传统的输入/输出流速度更快。NIO通过管道Channel和缓冲器Buffer来处理数据,可以把管道当成一个矿藏,缓冲器就是矿藏里的卡车。程序通过管道里的缓冲器进行数据交互,而不直接处理数据。程序要么从缓冲器获取数据,要么输入数据到缓冲器。
Java I/O流面试之道
|
20天前
|
存储 Java 关系型数据库
在Java开发中,数据库连接是应用与数据交互的关键环节。本文通过案例分析,深入探讨Java连接池的原理与最佳实践
在Java开发中,数据库连接是应用与数据交互的关键环节。本文通过案例分析,深入探讨Java连接池的原理与最佳实践,包括连接创建、分配、复用和释放等操作,并通过电商应用实例展示了如何选择合适的连接池库(如HikariCP)和配置参数,实现高效、稳定的数据库连接管理。
38 2
|
23天前
|
Java 数据格式 索引
使用 Java 字节码工具检查类文件完整性的原理是什么
Java字节码工具通过解析和分析类文件的字节码,检查其结构和内容是否符合Java虚拟机规范,确保类文件的完整性和合法性,防止恶意代码或损坏的类文件影响程序运行。
|
20天前
|
算法 Java 数据库连接
Java连接池技术,从基础概念出发,解析了连接池的工作原理及其重要性
本文详细介绍了Java连接池技术,从基础概念出发,解析了连接池的工作原理及其重要性。连接池通过复用数据库连接,显著提升了应用的性能和稳定性。文章还展示了使用HikariCP连接池的示例代码,帮助读者更好地理解和应用这一技术。
32 1
|
26天前
|
存储 安全 Java
深入理解Java中的FutureTask:用法和原理
【10月更文挑战第28天】`FutureTask` 是 Java 中 `java.util.concurrent` 包下的一个类,实现了 `RunnableFuture` 接口,支持异步计算和结果获取。它可以作为 `Runnable` 被线程执行,同时通过 `Future` 接口获取计算结果。`FutureTask` 可以基于 `Callable` 或 `Runnable` 创建,常用于多线程环境中执行耗时任务,避免阻塞主线程。任务结果可通过 `get` 方法获取,支持阻塞和非阻塞方式。内部使用 AQS 实现同步机制,确保线程安全。
|
26天前
|
安全 Java
Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧
【10月更文挑战第20天】Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧,包括避免在循环外调用wait()、优先使用notifyAll()、确保线程安全及处理InterruptedException等,帮助读者更好地掌握这些方法的应用。
17 1