转载

图解Java IO模型(一)

对于 Java 的 IO 模型,对于大多数小伙伴都不陌生。但是想要准确的讲述这些 IO 模型之间的不同特点以及应用的范围,可能有一部分小伙伴很难讲清楚。在这篇文章中,我将尽可能清楚的讲述这些问题,希望对于坐在屏幕面前的你有一定的帮助。

在这篇文章中,我会先叙述 Linux 的 IO 模型,因为对于一些基本概念同步、异步、阻塞、非阻塞 Linux 和 Java 都是一致的,而且 Linux 的模型和 Java 的 IO 模型有相似的地方,或者说 Java 的 IO 模型有参考 Linux 的 IO 模型的地方。

Linux IO 模型

概念

内核态:核心态代码拥有完全的底层资源控制权限,可以执行任何 CPU 指令,访问任何内存地址,其占有的处理机是不允许被抢占的。

用户态:是用户程序能够使用的指令,不能直接访问底层硬件和内存地址。用户态运行的程序必须委托系统调用来访问硬件和内存。

内核态会涉及到一些比较底层或者比较危险的指令。如果对于用户开放的话,会造成比较危险的状况,所以用户不能直接调用这些指令。用户需要借助系统调用才能执行一些相关的操作。

图解Java IO模型(一)

同步/异步关注的是消息通信机制 。

同步:就是在发出一个调用时,在没有得到结果之前, 该调用就不返回。 异步:调用在发出之后,这个调用就直接返回了,所以没有返回结果。

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

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

举个简单的例子。

淘宝双 11 活动,小图同学买了一本《图解 Java》的书,此时快递小哥正在派送快递的情景。

同步、异步

小图(线程/进程 A)给快递小哥(线程/进程 B)打电话(发起调用),询问送到家的时间(结果)。如果此时快递小哥说,5 分钟或者马上送到(结果)。小图(线程/进程 A)挂了电话(调用结束)。此时就是小图(线程/进程 A)和快递小哥(线程/进程 B)就是异步调用。

小图(线程/进程 A)给快递小哥(线程/进程 B)打电话(发起调用),询问送到家的时间(结果)。如果此时快递小哥说,我也不知道还要多久,我得先把手中的这几件快递先送完(不是结果)。小图(线程/进程 A)没挂电话(仍然调用)。过了一会,快递小哥送完那几件快递告诉小图,5 分钟后送到(结果)。此时就是小图(线程/进程 A)和快递小哥(线程/进程 B)就是同步调用。

同步、异步一般发生在不同的线程/进程之间。如果调用者发起调用,能马上获得调用结果,这就是异步调用。如果不能马上获得结果,需要等待被调用的线程/进程一段时间,才能获得结果,这就是同步调用。

阻塞、非阻塞

小图(线程/进程 A)给快递小哥(线程/进程 B)打电话(发起调用),但是快递小哥的电话在通话中。小图就一直等待快递小哥,也没挂电话。此时小图(线程/进程 A)处于阻塞状态。

小图(线程/进程 A)给快递小哥(线程/进程 B)打电话(发起调用),但是快递小哥的电话在通话中。小图听到快递小哥的电话在通话中,就挂断电话。之后看起了《图解 Java》公众号的文章(别的任务)。一会后,又给快递小哥打电话 …… 。此时小图(线程/进程 A)处于非阻塞状态。

阻塞、非阻塞一般发生在单个进程/线程中。当进程/线程想要进行 A 操作,但是不能立刻完成(需要其他的条件 C)。如果线程/进程一直等待,不执行其他的操作,即此时处于阻塞状态。如果该线程/进程又开始执行另外的操作 B,此时线程处于非阻塞状态。

图解Java IO模型(一)

Linux IO 模型

根据前面描述的内核态和用户态的相关概念。当应用程序发起文件调用的时候,用户态运行的程序必须委托系统调用来访问硬件和内存。这个过程主要分为两个阶段, 等待数据将数据从内核拷贝到用户空间 。根据这两个阶段中,调用进程和内核进程的表现,分为五种模型。

阻塞 IO 模型:

调用进程会一直阻塞,直到数据拷贝完成 。即应用程序调用一个 IO 函数,导致应用程序阻塞,等待数据准备好。数据准备好后,从内核拷贝到用户空间,IO 函数返回成功指示。 在整个调用过程中都是阻塞的。

非阻塞 IO 模型:

调用进程在等待数据的过程中,会不断轮询数据有没有准备好。数据准备好后,从内核拷贝到用户空间,IO 函数返回成功指示。在等待数据的过程中是非阻塞的;将数据从内核拷贝到用户空间是阻塞的。

IO 复用模型:

在非阻塞 IO 模型中,需要调用线程不断去轮询,检查数据有没有准备好,在这个过程中消耗了 CPU 大量的时间。如果有其他的线程帮助去检查多个线程数据的完成状态,这样会提高效率。

Linux 提供了 selectpollepoll 帮助我们。一个线程可以对多个 IO 端口进行监听,当 socket 有读写事件时分发到具体的线程进行处理。

select、poll、epoll 区别总结:

支持一个进程打开连接数 IO 效率 消息传递方式
select 32 位机器 1024 个,64 位 2048 个 IO 效率低 内核需要将消息传递到用户空间,都需要内核拷贝动作
poll 无限制,原因基于链表存储 IO 效率低 内核需要将消息传递到用户空间,都需要内核拷贝动作
epoll 有上限,但很大,2G 内存 20W 左右 只有活跃的 socket 才调用 callback,IO 效率高 通过内核与用户空间共享一块内存来实现

信号驱动式 I/O:

首先我们允许 Socket 进行信号驱动 IO,并安装一个信号处理函数,进程继续运行并不阻塞。当数据准备好时,进程会收到一个 SIGIO 信号,可以在信号处理函数中调用 I/O 操作函数处理数据。

异步 IO 模型:

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

图解Java IO模型(一)

本文使用 mdnice 排版

原文  https://juejin.im/post/5e6745dd6fb9a07ca03d636b
正文到此结束
Loading...