java 同步/异步IO和阻塞/非阻塞IO 关系和概念解析

I/O的模型

首先要声明的一点一定要把同步/异步 阻塞/非阻塞 以及I/O这三者的概念区别开来,同步大部分是阻塞
的,异步大部分是非阻塞的,但是它们之间并没有必然的因果关系

同步与异步

两者产生需要有个前提——是否有多个任务或事件发生,只有满足了这一前提,才有了同步和异步的概念

同步:如果有多个任务或者事件要发生,这些任务或者事件必须逐个地进行,一个事件或者任务
的执行会导致整个流程的暂时等待,这些事件没有办法并发地执行

异步:如果有多个任务或者事件发生,这些事件可以并发地执行,一个事件或者任务的执行不会
导致整个流程的暂时等待

再扩展一点,所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回。按照这个定义,
其实绝大多数函数都是同步调用。但是一般而言,我们在说同步、异步的时候,特指那些需要其他部件协作或
者需要一定时间完成的任务

举个例子,小明他妈(调用方)派小明(被调用方)去车站迎接客人,小明一直在车站等到客人到达,把客人
带回家,交给他妈,这就是同步调用。
小明嫌在车站等着无聊,改为每隔五分钟就出去看一次,立即回来告诉他妈客人到没到,这就是异步调用

阻塞和非阻塞

这两个概念的前提条件是某个事件或者任务在执行

阻塞:当某个事件或者任务在执行过程中,它发出一个请求操作,但是由于该请求操作需要的条件
不满足,那么就会一直在那等待,直至条件满足

非阻塞:当某个事件或者任务在执行过程中,它发出一个请求操作,如果该请求操作需要的条件不
满足,会立即返回一个标志信息告知条件不满足,不会一直在那等待

因此同步/异步和阻塞/非阻塞没有必联的关系,至于为什么有时候会感觉两者有联系呢?是因为java中为
了使多线程同步,会用上锁的方式锁住某一共享资源,等待锁的线程就被阻塞了,感觉上为了同步就有了阻
塞。其实不然,因为同步/异步和阻塞/非阻塞概念产生的前提是不一样的,前两者是多任务,后者是单任务
执行过程中遇到阻碍

阻塞/非阻塞, 它们是程序在等待消息(无所谓同步或者异步)时的状态
阻塞调用是指调用结果返回之前,当前线程会被挂起。有人也许会把阻塞调用和同步调用等同起来,实际上他是不同的。对于同步调用来说,很多时候当前线程还是激活的,只是从逻辑上当前函数没有返回而已。
非阻塞和阻塞的概念相对应,指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回。
还是小明他妈(调用方)派小明(被调用方)去车站迎接客人,在客人到来之前,小明他妈什么都不干,专心等待客人,这就是阻塞调用
后来,小明他妈变聪明了,在客人到来之前,她可以洗菜、拖地、听听歌,客人来了之后再招待客人,这就是非阻塞调用

I/O的操作过程

通常来说,IO操作包括:对硬盘的读写、对socket的读写以及外设的读写,并且需要进行用户空间和内核空
间的区分(用户空间就是普通的用户进程,内核空间就是内核进程,只有内核空间才可以直接范围磁盘等物理
I/O设备)

用户空间产生一个读请求,请求再转交由内核空间执行
1. 内核检查读取的数据是否就绪
2. 如果就绪,内核将数据从内核空间复制到用户空间(内存上拷贝)

阻塞I/O与非阻塞I/O

阻塞I/O:内核在检查数据未就绪时,会一直等待,直到数据就绪
非阻塞I/O:如果数据没有就绪,则会返回一个标志信息告知用户线程当前要读的数据没有就绪

它们的区别在于I/O的第一阶段,阻塞是选择等待,非阻塞是返回一个标志信息

那么非阻塞I/O的优势在哪里呢?使用阻塞I/O处理网络连接时,有10000个连接就要开10000个线程,无论
有没有数据到来,处理某一连接的线程必须“忠实地阻塞”。而非阻塞I/O就不需要这样,它可以维护一个1000
个线程的线程池,当有数据就绪时,启动一个线程去接受数据,当没有数据时,线程不需要等待,直接就可以
回到池中,等待被调度到去接受其它连接。因此非阻塞I/O非常适合连接多但传输的数据内容不大的情况,如
果连接少数据多,阻塞I/O更容易编程

同步I/O和异步I/O

事实上,同步IO和异步IO模型是针对用户线程和内核的交互来说的,即数据是否就绪的消息传递机制

同步IO:当用户发出IO请求操作之后,如果数据没有就绪,需要通过用户线程或者内核不断地去轮询数据是否
就绪,当数据就绪时,再将数据从内核拷贝到用户线程

异步IO:只有IO请求操作的发出是由用户线程来进行的,内核自动完成检查数据是否就绪和将数据拷贝
到用户空间的过程,然后发送通知告知用户线程IO操作已经完成。

在《Unix网络编程》书中说到:

A synchronous I/O operation causes the requesting process to be blocked until
that I/O operation completes.

An asynchronous I/O operation does not cause the requesting process to be blocked.

也许会奇怪在这里说到同步I/O会阻塞,异步I/O不会阻塞,那么是不是意味着同步I/O必然是阻塞I/O呢?
其实不然,同步/异步I/O阻塞的地方和之前说的阻塞/非阻塞IO阻塞的地方不一样。同步/异步I/O阻塞的地方
是用户或内核需要轮询数据是否就绪,而阻塞/非阻塞IO是阻塞在数据未就绪的状态下。

同步阻塞I/O=UNIX 阻塞I/O

java 传统IO模型(java.io.* 又称为BIO Blocking-IO) 用户线程发起I/O请求,再转交给内核,轮询
内核数据是否就绪(同步I/O),内核通知用户空间数据未就绪,用户线程选择等待(阻塞I/O)

同步非阻塞I/O=UNIX 非阻塞I/O

用户线程发起I/O请求,再转交给内核,并不断轮询内核数据是否就绪(同步I/O),内核通知用户空间数据未
就绪,用户线程直接返回标志信息不等待(非阻塞I/O)

while(true){
    data = socket.read();
    if(data!= error){
        处理数据
        break;
    }
}

同步非阻塞I/O=UNIX 多路复用I/O

java nio模型(java.nio.* jdk1.4/1.5 NoBlocking-IO) 在多路复用IO模型中,会有一个线程不断
去轮询多个socket的状态,只有当socket真正有读写事件时,才真正调用实际的IO读写操作,只需要使用
一个线程就可以管理多个socket

在Java NIO中,是通过selector.select()去查询每个通道是否有到达事件,如果没有数据到达,则跳过
有数据到达,就处理数据。因此它可以归类为同步非阻塞I/O

其实就是一个线程轮询方式(同步I/O)管理多个非阻塞(非阻塞I/O)的通道(channel),多路复用在这个管理
线程可管理多个I/O通道

异步阻塞I/O=UNIX 信号驱动I/O

在信号驱动IO模型中,当用户线程发起一个IO请求操作,会给对应的socket注册一个信号函数,然后用户
线程会继续执行,当内核数据就绪时会发送一个信号给用户线程(异步I/O),用户线程接收到信号之后,便
在信号函数中调用IO读写操作来进行实际的IO请求操作(阻塞I/O 因为将数据从内核空间复制到用户空间时
用户线程会被阻塞)

异步非阻塞I/O=UNIX 异步IO

java的 nio.2模型(jdk1.7 AsynchronousChannel)
真正的异步非阻塞I/O,最理想的I/O模型。当用户线程发起read操作之后,立刻就可以开始去做其它的事,
而另一方面,从内核的角度,当它受到一个asynchronous read之后,它会立刻返回,说明read请求已经
成功发起了,因此不会对用户线程产生任何block。然后,内核会等待数据准备完成,然后将数据拷贝到用
户线程,当这一切都完成之后,内核会给用户线程发送一个信号,告诉它read操作完成了。也就说用户线程
完全不需要实际的整个IO操作是如何进行的,只需要先发起一个请求,当接收内核返回的成功信号时表示IO
操作已经完成,可以直接去用户空间使用数据了。

在I/O的第一第二阶段,用户线程都不需要管,不需要轮询是否就绪(异步),也不需要等待就绪后数据拷贝
的时间(非阻塞),当然也不存在如果没就绪的话线程的等待(非阻塞)

总结一下,除了异步非阻塞I/O=UNIX 异步IO 其他I/O模型都是阻塞的:
1. 同步阻塞I/O=UNIX 阻塞I/O 同步在用户线程轮询,阻塞在数据未就绪和就绪后的复制
2. 同步非阻塞I/O=UNIX 非阻塞I/O 同步在用户线程轮询,数据未就绪时不阻塞,阻塞在就绪后复制
3. 同步非阻塞I/O=UNIX 多路复用I/O 同步在用户线程轮询,数据未就绪时不阻塞,阻塞在就绪后复制
但与UNIX 非阻塞I/O区别在于,由一个线程来维护多个非阻塞通道,属于轻微加强版
4. 异步阻塞I/O=UNIX 信号驱动I/O 内核通知是否就绪,数据未就绪时不阻塞,阻塞在就绪后复制
5. 异步非阻塞I/O=UNIX 异步IO 内核通知是否就绪,绝对不阻塞

同步/异步IO——在于消息通知
阻塞/非阻塞IO——在于数据未就绪时阻塞么?数据就绪时,需要用户线程等待数据从内核复制到用户空间么?
异步I/O需要操作系统的支持

展开阅读全文

没有更多推荐了,返回首页