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

详解 Java 中 4 种 I/O 模型

作者头像
Java技术栈
发布于 2019-01-02 02:14:11
发布于 2019-01-02 02:14:11
6900
举报
文章被收录于专栏:Java技术栈Java技术栈

来源:ncoding.com/2018/04/02/java/io.html 整编:Java技术栈(公众号ID:javastack)

同步、异步、阻塞、非阻塞都是和I/O(输入输出)有关的概念,最简单的文件读取就是I/O操作。而在文件读取这件事儿上,可以有多种方式。

本篇会先介绍一下I/O的基本概念,通过一个生活例子来分别解释下这几种I/O模型,以及Java支持的I/O模型。

基本概念

在解释I/O模型之前,我先说明一下几个操作系统的概念

文件描述符fd

文件描述符(file descriptor)是计算机科学中的一个术语,是一个用于表述指向文件的引用的抽象化概念。

文件描述符在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。 当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。 在程序设计中,一些涉及底层的程序编写往往会围绕着文件描述符展开。文件描述符这一概念往往只适用于UNIXLinux这样的操作系统。

缓存I/O

缓存I/O又被称作标准I/O,大多数文件系统的默认I/O操作都是缓存I/O。在Linux的缓存I/O机制中, 操作系统会将I/O的数据缓存在文件系统的页缓存中,也就是说,数据会先被拷贝到操作系统内核的缓冲区中, 然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。

缓存I/O的缺点是数据在传输过程中需要在应用程序地址空间和内核进行多次数据拷贝操作,这些数据拷贝操作所带来的CPU以及内存开销是非常大的。

下面我以一个生活中烧开水的例子来形象解释一下同步、异步、阻塞、非阻塞概念。

同步和异步

说到烧水,我们都是通过热水壶来烧水的。在很久之前,科技还没有这么发达的时候,如果我们要烧水, 需要把水壶放到火炉上,我们通过观察水壶内的水的沸腾程度来判断水有没有烧开。

随着科技的发展,现在市面上的水壶都有了提醒功能,当我们把水壶插电之后,水壶水烧开之后会通过声音提醒我们水开了。

对于烧水这件事儿来说,传统水壶的烧水就是同步的,高科技水壶的烧水就是异步的。

同步请求

A调用B,B的处理是同步的,在处理完之前他不会通知A,只有处理完之后才会明确的通知A。

异步请求

A调用B,B的处理是异步的,B在接到请求后先告诉A我已经接到请求了,然后异步去处理,处理完之后通过回调等方式再通知A。

所以说,同步和异步最大的区别就是被调用方的执行方式和返回时机。 同步指的是被调用方做完事情之后再返回,异步指的是被调用方先返回,然后再做事情,做完之后再想办法通知调用方。

阻塞和非阻塞

还是那个烧水的例子,当你把水放到水壶里面,按下开关后,你可以坐在水壶前面,别的事情什么都不做, 一直等着水烧好。你还可以先去客厅看电视,等着水开就好了。

对于你来说,坐在水壶前面等就是阻塞的,去客厅看电视等着水开就是非阻塞的。

阻塞请求

A调用B,A一直等着B的返回,别的事情什么也不干。

非阻塞请求

A调用B,A不用一直等着B的返回,先去忙别的事情了。

所以说,阻塞和非阻塞最大的区别就是在被调用方返回结果之前的这段时间内,调用方是否一直等待。 阻塞指的是调用方一直等待别的事情什么都不做。非阻塞指的是调用方先去忙别的事情。

阻塞、非阻塞和同步、异步的区别

首先,前面已经提到过,阻塞、非阻塞和同步、异步其实针对的对象是不一样的。

给我大声念三遍下面的句子

阻塞、非阻塞说的是调用者。同步、异步说的是被调用者。

阻塞、非阻塞说的是调用者。同步、异步说的是被调用者。

阻塞、非阻塞说的是调用者。同步、异步说的是被调用者。

有人认为阻塞和同步是一回事儿,非阻塞和异步是一回事。但是这是不对的。

同步包含阻塞和非阻塞

我们是用传统的水壶烧水。在水烧开之前我们一直做在水壶前面,等着水开。这就是阻塞的。

我们是用传统的水壶烧水。在水烧开之前我们先去客厅看电视了,但是水壶不会主动通知我们, 需要我们时不时的去厨房看一下水有没有烧开,这就是非阻塞的。

异步包含阻塞和非阻塞

我们是用带有提醒功能的水壶烧水。在水烧发出提醒之前我们一直做在水壶前面,等着水开。这就是阻塞的。

我们是用带有提醒功能的水壶烧水。在水烧发出提醒之前我们先去客厅看电视了,等水壶发出声音提醒我们。这就是非阻塞的。推荐阅读:46 道阿里巴巴 Java 面试题,你会几道?

Unix中的五种I/O模型

对于一次I/O访问(以read举例),数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。 所以说,当一个read操作发生时,它会经历两个阶段:

第一阶段:等待数据准备 (Waiting for the data to be ready)。

第二阶段:将数据从内核拷贝到进程中 (Copying the data from the kernel to the process)。

对于socket流而言

第一阶段:通常涉及等待网络上的数据分组到达,也就是被复制到内核的某个缓冲区。

第二阶段:把数据从内核缓冲区复制到应用进程缓冲区。

Unix下五种I/O模型:

  1. 同步阻塞I/O
  2. 同步非阻塞I/O
  3. I/O多路复用(select和poll)
  4. 信号驱动I/O(SIGIO)
  5. 异步非阻塞 IO

同步阻塞I/O

阻塞I/O下请求无法立即完成则保持阻塞,阻塞I/O分为如下两个阶段。

阶段1:等待数据就绪。网络I/O的情况就是等待远端数据陆续抵达,也就是网络数据被复制到内核缓存区中,磁盘I/O的情况就是等待磁盘数据从磁盘上读取到内核态内存中。

阶段2:数据拷贝。出于系统安全,用户态的程序没有权限直接读取内核态内存,因此内核负责把内核态内存中的数据拷贝一份到用户态内存中。

这两个阶段必须都完成后才能继续下一步操作

所以,blocking IO的特点就是在IO执行的两个阶段都被block了。

同步非阻塞I/O

就是阶段1的时候用户进程可选择做其他事情,通过轮询的方式看看内核缓冲区是否就绪。如果数据就绪,再去执行阶段2。

也就是说非阻塞的recvform系统调用调用之后,进程并没有被阻塞,内核马上返回给进程,如果数据还没准备好, 此时会返回一个error。进程在返回之后,可以干点别的事情,然后再发起recvform系统调用。

重复上面的过程, 循环往复的进行recvform系统调用。这个过程通常被称之为轮询。轮询检查内核数据,直到数据准备好, 再拷贝数据到进程,进行数据处理。需要注意,第2阶段的拷贝数据整个过程,进程仍然是属于阻塞的状态。推荐阅读:Java 8 开发的 4 大顶级技巧

在linux下,可以通过设置socket使其变为non-blocking。当对一个non-blocking socket执行读操作时,流程如图所示:

所以,nonblocking IO的特点是用户进程需要不断的主动询问kernel数据好了没有。

I/O多路复用

我这里只想重点解释一下I/O多路复用这种模型,因为现在用的最多。很多地方也称为事件驱动IO模型,只是叫法不同,意思都一个样。

IO多路复用是指内核一旦发现进程指定的一个或者多个IO条件准备读取,它就通知该进程。

目前支持I/O多路复用的系统调用有 select、pselect、poll、epoll,I/O多路复用就是通过一种机制,一个进程可以监视多个描述符, 一旦某个文件描述符fd就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。

但select、pselect、poll、epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的, 而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。

相比较于同步非阻塞I/O,它的改进的地方在于,原来需要用户进程去轮询的这事儿交给了内核线程帮你完成, 而且这个内核线程可以等待多个socket,能实现同时对多个IO端口进行监听。

多路复用的特点是通过一种机制一个进程能同时等待IO文件描述符,内核监视这些文件描述符(套接字描述符), 其中的任意一个进入读就绪状态,select, poll,epoll函数就可以返回。对于监视的方式, 又可以分为 select, poll, epoll三种方式。

所以,如果处理的连接数不是很高的话,使用select/epoll的web server不一定比使用多线程 + 阻塞IO的web server性能更好,可能延迟还更大。 也就是说,select/epoll的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接。推荐阅读:Spring Boot 的 10 个核心模块

高并发的程序一般使用同步非阻塞方式而非多线程 + 同步阻塞方式。要理解这一点,首先要扯到并发和并行的区别。 比如去某部门办事需要依次去几个窗口,办事大厅里的人数就是并发数,而窗口个数就是并行度。 也就是说并发数是指同时进行的任务数(如同时服务的 HTTP 请求),而并行数是可以同时工作的物理资源数量(如 CPU 核数)。

通过合理调度任务的不同阶段,并发数可以远远大于并行度,这就是区区几个 CPU 可以支持上万个用户并发请求的奥秘。 在这种高并发的情况下,为每个任务(用户请求)创建一个进程或线程的开销非常大。而同步非阻塞方式可以把多个 IO 请求丢到后台去, 这就可以在一个进程里服务大量的并发 IO 请求。

IO多路复用归为同步阻塞模式

异步非阻塞 IO

相对于同步IO,异步IO不是顺序执行。用户进程进行aio_read系统调用之后,无论内核数据是否准备好,都会直接返回给用户进程, 然后用户态进程可以去做别的事情。等到socket数据准备好了,内核直接复制数据给进程,然后从内核向进程发送通知。IO两个阶段, 进程都是非阻塞的。

Linux提供了AIO库函数实现异步,但是用的很少。目前有很多开源的异步IO库,例如libevent、libev、libuv。异步过程如下图所示:

更详细的分析可参考 聊聊Linux5种IO模型

Java中四种I/O模型

上一章所述Unix中的五种I/O模型,除信号驱动I/O外,Java对其它四种I/O模型都有所支持。

  1. Java传统IO模型即是同步阻塞I/O
  2. NIO是同步非阻塞I/O
  3. 通过NIO实现的Reactor模式即是I/O多路复用模型的实现
  4. 通过AIO实现的Proactor模式即是异步I/O模型的实现
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2018-12-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Java技术栈 微信公众号,前往查看

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

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

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
彩虹女神跃长空,Go语言进阶之Go语言高性能Web框架Iris项目实战-项目入口与路由EP01
    书接上回,我们已经安装好Iris框架,并且构建好了Iris项目,同时配置了fresh自动监控项目的实时编译,万事俱备,只欠东风,彩虹女神蓄势待发。现在我们来看看Iris的基础功能,如何编写项目入口文件以及配置路由系统。
用户9127725
2022/09/22
3790
彩虹女神跃长空,Go语言进阶之Go语言高性能Web框架Iris项目实战-项目入口与路由EP01
彩虹女神跃长空,Go语言进阶之Go语言高性能Web框架Iris项目实战-模板与数据库EP02
    书接上回,上次我们搭建好了项目入口文件,同时配置了路由体系,接着就可以配置项目的模板了,这里我们采用Iris内置的模板引擎,事实上,采用模板引擎并不意味着前后端耦合,模板中的数据保持其独立性即可,也就是说模板的数据操作交互方式采用http接口请求的形式,Iris并不参与模板逻辑,只返回Json格式的数据即可。前端集成数据双向绑定机制的框架Vue.js。
用户9127725
2022/09/23
6360
层次分明井然有条,Go lang1.18入门精炼教程,由白丁入鸿儒,Go lang包管理机制(package)EP10
    Go lang使用包(package)这种概念元素来统筹代码,所有代码功能上的可调用性都定义在包这个级别,如果我们需要调用依赖,那就“导包”就行了,无论是内部的还是外部的,使用import关键字即可。但事情往往没有那么简单,Go lang在包管理机制上走了不少弯路,虽然1.18版本的包管理已经趋于成熟,但前事不忘后事之师,我们还是需要了解一下这段历史。
用户9127725
2022/09/21
2520
2022-04-05:golang中go.mod文件,做框架开发需要解析,请问如何解析?
2022-04-05:golang中go.mod文件,做框架开发需要解析,请问如何解析?
福大大架构师每日一题
2022/04/05
2080
Golang中的包管理工具 - Go Modules
在Go1.5之前使用GOROOT和GOPATH这2个系统环境变量来决定包的位置,对于开发者主要使用GOPATH。GOPATH 解决了第三方源码依赖的问题,看一下我本机 $GOPATH/src 下的目录:
猿哥
2019/08/01
1.6K0
用go-module作为包管理器搭建go的web服务器
本篇博客主要介绍了如何从零开始,使用Go Module作为依赖管理,基于Gin来一步一步搭建Go的Web服务器。并使用Endless来使服务器平滑重启,使用Swagger来自动生成Api文档。
SH的全栈笔记
2019/10/20
1.7K0
The Things Network LoRaWAN Stack V3 学习笔记 1.2 源码编译 - 190821
源码编译是重头戏,这节笔记记录如何使用 make 命令编译相关部件。由于部分包在墙外,带来了一点麻烦,还分享一个 GO 翻墙利器。
twowinter
2020/04/17
1.1K0
Go语言学习(十)| module 使用
go module 是在go 1.11版本才开始有的,需要将环境变量 GO111MODULE 设置为 on 才能正常使用
Mervyn
2020/07/21
3910
【One by one系列】一步步学习Golang web框架Gin
cd $gopath\src\github.com\carfield\Webapi
DDGarfield
2022/06/23
2430
彩虹女神跃长空,Go语言进阶之Go语言高性能Web框架Iris项目实战-JWT和中间件(Middleware)的使用EP07
    前文再续,上一回我们完成了用户的登录逻辑,将之前用户管理模块中添加的用户账号进行账号和密码的校验,过程中使用图形验证码强制进行人机交互,防止账号的密码被暴力破解。本回我们需要为登录成功的用户生成Token,并且通过Iris的中间件(Middleware)进行鉴权操作。
用户9127725
2022/09/27
5880
彩虹女神跃长空,Go语言进阶之Go语言高性能Web框架Iris项目实战-JWT和中间件(Middleware)的使用EP07
彩虹女神跃长空,Go语言进阶之Go语言高性能Web框架Iris项目实战-完善用户管理EP04
    书接上回,上一回我们完成了用户管理页面的构建,并且通过前端的Vue.js框架动态地获取表单数据,同时异步请求后端Iris接口进行入库操作,过程中使用函数封装可复用的逻辑。 本回我们将继续完善用户管理功能。
用户9127725
2022/09/23
6030
彩虹女神跃长空,Go语言进阶之Go语言高性能Web框架Iris项目实战-完善用户管理EP04
彩虹女神跃长空,Go语言进阶之Go语言高性能Web框架Iris项目实战-项目结构优化EP05
    前文再续,上一回我们完成了用户管理模块的CURD(增删改查)功能,功能层面,无甚大观,但有一个结构性的缺陷显而易见,那就是项目结构过度耦合,项目的耦合性(Coupling),也叫耦合度,进而言之,模块之间的关系,是对项目结构中各模块间相互联系紧密程度的一种量化。耦合的强弱取决于模块间调用的复杂性、调用模块之间的方式以及通过函数或者方法传送数据对象的多少。模块间的耦合度是指模块之间的依赖关系,包括包含关系、控制关系、调用关系、数据传递关系以及依赖关系。项目模块的相互依赖越多,其耦合性越强,同时表明其独立性越差,愈加难以维护。
用户9127725
2022/09/27
5870
GO 依赖管理工具go Modules(官方推荐)
以前写过一篇关于go管理依赖包工具 dep的文章,当时认为dep将会成为官方依赖工具,现在看来是自己图样图斯内幕破了,正如官方一直提到dep是“official experiment”官方实验项目的那样,随着go modules 在go1.11版推出,go1.12版功能不断改进,再到go1.13版完善优化,正式扶正。预计dep将来也只能定格在“official experiment”了。
孤烟
2020/09/27
1.8K1
Go Modules 终极入门
Go modules 是 Go 语言中正式官宣的项目依赖解决方案,Go modules(前身为vgo)于 Go1.11 正式发布,在 Go1.14 已经准备好,并且可以用在生产上(ready for production)了,Go 官方也鼓励所有用户从其他依赖项管理工具迁移到 Go modules。
madneal
2020/03/10
1.9K0
Go Modules 终极入门
Golang Module的使用 顶
注意:go mod 还有一些其他比较有意思的工具,比如可以打印依赖树,比如可以查看哪些模块在哪些包引用了
BGBiao
2019/09/09
1.3K0
彩虹女神跃长空,Go语言进阶之Go语言高性能Web框架Iris项目实战-用户系统EP03
    前文再续,之前一篇我们已经配置好了数据库以及模板引擎,现在可以在逻辑层编写具体业务代码了,博客平台和大多数在线平台一样,都是基于用户账号体系来进行操作,所以我们需要针对用户表完成用户账号的CURD操作。
用户9127725
2022/09/23
6140
彩虹女神跃长空,Go语言进阶之Go语言高性能Web框架Iris项目实战-用户系统EP03
Go mod包依赖管理工具使用详解
对比上面几点: 目前做的最好的也就 maven了,gradle没有使用过,不知道。
OwenZhang
2021/12/08
1K0
Go mod包依赖管理工具使用详解
彩虹女神跃长空,Go语言进阶之Go语言高性能Web框架Iris项目实战-登录与图形验证码(captcha)EP06
    书接上回,上一回我们按照“低耦合高内聚”的组织架构方针对项目的整体结构进行了优化,本回将会继续编写业务,那就是用户的登录逻辑,将之前用户管理模块中添加的用户账号进行账号和密码的校验,校验通过后留存当前登录用户的信息,过程中使用图形验证码强制进行人机交互,防止账号的密码被暴力破解。
用户9127725
2022/09/26
4490
彩虹女神跃长空,Go语言进阶之Go语言高性能Web框架Iris项目实战-登录与图形验证码(captcha)EP06
Hyperledger fabric-sample 部署测试(基于 Ubuntu)
配置 Go 环境,在 $HOME/.profile 或者 /etc/profile 中添加:
子乾建建-Jeff
2021/02/04
1.8K0
go mod使用
最近由于换工作,开始交接工作。整理以前的工作内容,由于组内就我一个在做go和大数据。 所以开发没有规划,当时是怎么快怎么来。go也是使用最传统的go path的方式管理的。都是手动管理依赖的。现在交接给他人,需要多人开发,发现很多问题。比如版本问题,各种依赖的问题等等。
若与
2020/05/18
1.6K0
推荐阅读
彩虹女神跃长空,Go语言进阶之Go语言高性能Web框架Iris项目实战-项目入口与路由EP01
3790
彩虹女神跃长空,Go语言进阶之Go语言高性能Web框架Iris项目实战-模板与数据库EP02
6360
层次分明井然有条,Go lang1.18入门精炼教程,由白丁入鸿儒,Go lang包管理机制(package)EP10
2520
2022-04-05:golang中go.mod文件,做框架开发需要解析,请问如何解析?
2080
Golang中的包管理工具 - Go Modules
1.6K0
用go-module作为包管理器搭建go的web服务器
1.7K0
The Things Network LoRaWAN Stack V3 学习笔记 1.2 源码编译 - 190821
1.1K0
Go语言学习(十)| module 使用
3910
【One by one系列】一步步学习Golang web框架Gin
2430
彩虹女神跃长空,Go语言进阶之Go语言高性能Web框架Iris项目实战-JWT和中间件(Middleware)的使用EP07
5880
彩虹女神跃长空,Go语言进阶之Go语言高性能Web框架Iris项目实战-完善用户管理EP04
6030
彩虹女神跃长空,Go语言进阶之Go语言高性能Web框架Iris项目实战-项目结构优化EP05
5870
GO 依赖管理工具go Modules(官方推荐)
1.8K1
Go Modules 终极入门
1.9K0
Golang Module的使用 顶
1.3K0
彩虹女神跃长空,Go语言进阶之Go语言高性能Web框架Iris项目实战-用户系统EP03
6140
Go mod包依赖管理工具使用详解
1K0
彩虹女神跃长空,Go语言进阶之Go语言高性能Web框架Iris项目实战-登录与图形验证码(captcha)EP06
4490
Hyperledger fabric-sample 部署测试(基于 Ubuntu)
1.8K0
go mod使用
1.6K0
相关推荐
彩虹女神跃长空,Go语言进阶之Go语言高性能Web框架Iris项目实战-项目入口与路由EP01
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档