# javaio **Repository Path**: sunt/javaio ## Basic Information - **Project Name**: javaio - **Description**: java IO 模型学习及示例代码 - **Primary Language**: Java - **License**: LGPL-2.1 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2017-06-06 - **Last Updated**: 2020-12-18 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # java IO模型 ## 阻塞和非阻塞 * 阻塞:线程进行读写时,如果没有东西读或者暂时不可写,程序就会陷入等待状态,直到读到东西或者可写为止。 * 非阻塞:如果没有东西可读或者不可写,读写函数立即返回,而不等待。 ## 同步和异步 * 同步:用户操作后等待或者轮询的去查看操作是否就绪 * 异步:用户操作后便开始其他的动作,而这个操作完成后用户会得到完成通知。 ## Unix的IO模型 * 阻塞IO * 非阻塞IO * IO复用 * 信号驱动的IO * 异步IO ### 阻塞IO 独立IO线程配合,java的BIO就是这种模型,一个Socket需要一个独立线程,因此会带来线程膨胀的问题。 有两个阶段: * 第一阶段:等待数据就绪。网络I/O的话就是等待数据抵达。磁盘IO的情况等待磁盘数据从磁盘上读取到内核态内存中 * 第二阶段:数据从内核拷贝到进程。出于系统安全,用户态程序没有权限直接读取到内核态内存,因此内核负责把内核态内存中数据拷贝到用户态内存。 ![](http://o7tc5fosm.bkt.clouddn.com/14963720485310.jpg) ### 非阻塞IO * socket设置为NONBLOCK(非阻塞)就是告诉内核,当所有的IO操作无法完成时,不要讲进程睡眠,而是返回一个错误码(EWOULDBLOCK),这样请求就不会阻塞。 * IO操作函数将不断的测试数据是否已准备好,直到数据准备好为止。在这个过程中,虽然用户线程每次发起IO请求后可以立即返回,但是不断轮询,重复请求,消耗的大量CPU资源。 * 数据准备好,从内核拷贝到用户空间 * 一般很少直接使用这种模型,而是在其他I/O模型中使用这一特性。 ![](http://o7tc5fosm.bkt.clouddn.com/14963722881536.jpg) ### IO复用/异步阻塞IO I/O 多路复用会用到 select 或者 poll 函数,这两个函数也会使进程阻塞,但是和阻塞 I/O 所不同的的,这两个函数可以同时阻塞多个 I/O 操作。而且可以同时对多个读操作,多个写操作的 I/O 函数进行检测,直到有数据可读或可写时,才真正调用 I/O 操作函数。 ![](http://o7tc5fosm.bkt.clouddn.com/14963728520567.jpg) 从流程上来看,使用 select 函数进行 I/O 请求和同步阻塞模型没有太大的区别,甚至还多了添加监视 socket,以及调用 select 函数的额外操作,效率更差。但是,使用 select 以后最大的优势是用户可以在一个线程内同时处理多个 socket 的 I/O 请求。用户可以注册多个 socket,然后不断地调用 select 读取被激活的 socket,即可达到在同一个线程内同时处理多个 I/O 请求的目的。而在同步阻塞模型中,必须通过多线程的方式才能达到这个目的。 调用 select / poll 该方法由一个用户态线程负责轮询多个 socket,直到某个阶段1的数据就绪,再通知实际的用户线程执行阶段2的拷贝。 通过一个专职的用户态线程执行非阻塞I/O轮询,模拟实现了阶段一的异步化 ### 信号驱动 I/O(SIGIO) 首先我们允许 socket 进行信号驱动 I/O,并安装一个信号处理函数,进程继续运行并不阻塞。当数据准备好时,进程会收到一个 SIGIO 信号,可以在信号处理函数中调用 I/O 操作函数处理数据。 ![](http://o7tc5fosm.bkt.clouddn.com/14963737598722.jpg) ### 异步 I/O 调用 aio_read 函数,告诉内核描述字,缓冲区指针,缓冲区大小,文件偏移以及通知的方式,然后立即返回。当内核将数据拷贝到缓冲区后,再通知应用程序。 告知内核,当整个过程(包括阶段1和阶段2)全部完成时,通知应用程序来读数据。 ![](http://o7tc5fosm.bkt.clouddn.com/14963745772823.jpg) ### 几种IO模型比较 前四种模型的区别是阶段1不相同,阶段2基本相同,都是将数据从内核拷贝到调用者的缓冲区。而异步 I/O 的两个阶段都不同于前四个模型。 同步 I/O 操作引起请求进程阻塞,直到 I/O 操作完成。异步 I/O 操作不引起请求进程阻塞。 ![](http://o7tc5fosm.bkt.clouddn.com/14963747183155.jpg) ### 同步IO 一个IO操作结束之后才返回,效率会比较低,但是对应用来说编程方式会简单。java的BIO和NIO都是使用这种方式处理数据的。 ### 异步IO 异步IO请求只是写入缓存,从缓存到硬盘是否成功不可知,因此异步IO相当于把一个IO拆成两个部分,一个是发起请求,二是获取处理结果。因此增加的系统复杂度,但是性能是最好的。 ## Java 的IO模型 * BIO 用户进程在发起一个IO操作以后,必须等待IO操作的完成,只有当真正完成了IO操作以后,用户进程才能运行 * NIO 一种同步非阻塞的I/O模型,也是I/O多路复用的基础。 ![](http://o7tc5fosm.bkt.clouddn.com/14967189811712.jpg) * AIO:JAVA AIO(NIO.2)异步非阻塞,一个有效请求一个线程 * Future 即提交一个 I/O 操作请求,返回一个 Future。然后您可以对 Future 进行检查,确定它是否完成,或者阻塞 IO 操作直到操作正常完成或者超时异常。其实可以认为是一个同步IO。 * Callback 即提交一个 I/O 操作请求,并且指定一个 CompletionHandler。当异步 I/O 操作完成时,便发送一个通知,此时这个 CompletionHandler 对象的 completed 或者 failed 方法将会被调用。 在此种模式下,用户进程只需要发起一个IO操作然后立即返回,等IO操作真正的完成以后,应用程序会得到IO操作完成的通知,此时用户进程只需要对数据进行处理就好了,不需要进行实际的IO读写操作,因为真正的IO读取或者写入操作已经由内核完成了 从IO到NIO有两方面变化: * 面向流 到 面向块(缓冲) * JAVA IO 是面向流的,每次从流中读取一个或者多个字节,知道读取完。另外不能前后移动流的数据 * NIO是面向块的。数据会被读取(写入)到缓冲区,需要时可在缓冲区处理数据。 * 阻塞 到 非阻塞 * JAVA IO 的各种流都是阻塞的。到有线程调用read 或者write方法时,该线程被阻塞。 * NIO是非阻塞,请求是不会不会阻塞当前线程,所以一个线程可以管理多个输入,输出通道。 ### 两种高性能的IO模式 * 多线程 客户端来一个连接请求,服务器端就会新建一个线程来处理该请求的读写事件。 优点:服务端和客户端都是处理方便 缺点:线程资源占得过大 * 线程池 线程池。创建固定大小的线程池,来一个新建链接请求就从线程池取一个空闲线程来处理,处理完成后在吧线程交回线程池,使得线程可以重用。 线程池的缺点:如果都是长连接,就会导致线程池中线程都被占用,新来的连接由于没有可用线程就会导致客户端连接失败。线程池是和大量短连接应用。 * Reactor Reactor模式,会先对每个client注册感兴趣的事件,然后有个线程专门去轮询每个client是否有事件发生,当有时间发生时,便顺序处理事件,所有事件处理完成后,再去轮询其他事件。 多路复用IO就是采用Reactor模式。 * Proactor Proactor模式,当检测到有事件发生时,会新起一个异步操作,然后交由内核线程去处理,当内核线程完成IO操作之后,便发送一个通知告知操作已完成。异步IO模型就是Proactor模式。