本文内容涉及同步与异步,阻塞与非阻塞,BIO、NIO、AIO等概念,这块内容本身比较复杂,很难用三言两语说明白,而书上的定义更不容易理解是什么意思。下面这些内容是按照我的理解,以我认为尽可能简单易懂的语言组织出来,希望能够帮助理解这些概念。
1. 同步与异步
同步与异步的概念,关注的是 消息通信机制
同步是指发出一个请求,在没有得到结果之前该请求就不返回结果,请求返回时,也就得到结果了。
比如洗衣服,把衣服放在洗衣机里,没有洗好之前我们一直看着, 直到洗好了才拿出来晾晒。
异步是指发出一个请求后,立刻得到了回应,但没有返回结果。这时我们可以再处理别的事情(发送其他请求),所以这种方式需要我们通过状态主动查看是否有了结果, 或者可以设置一个回调来通知调用者。
比如洗衣服时,把衣服放到洗衣机里,我们就可以去做别的事情,过会儿来看看有没有洗好(通过状态查询);或者我们设置洗衣机洗完后响铃来通知我们洗好了(回调通知)
2. 阻塞与非阻塞
阻塞与非阻塞很容易和同步与异步混淆,但两者关注点是不一样的。 阻塞与非阻塞关注的是 程序在等待调用结果时的状态
阻塞有个明显的特征就是线程通常是处于BLOCKED状态(BIO中的read()操作时,线程阻塞是JVM配合OS完成的,此时Java获取到线程的状态仍是RUNNABLE但它确实已经被阻塞了)
如果要拿同步来做比较的话,同步通信方式中的线程在发送请求之后等待结果这个过程中应该处于RUNNABLE状态,同步必须一步一步来完成,就像是代码必须执行完一行才能执行下一行, 所以必须等待这个请求返回之后才可进行下一个请求, 即使等待结果的时间长,也是在执行这个请求的过程中。而异步则不用等上一条执行完, 可以先执行别的代码,等请求有了结果再来获取结果。
3. IO模型
Java中的IO操作是JVM配合操作系统来完成的。对于一个IO的读操作,数据会先被拷贝到操作系统内核的缓冲区中,然后从操作系统内核的缓冲区拷贝到应用程序的地址空间。所以整个过程可分为两个阶段:
根据这两个阶段,产生了常见的几种不同的IO模型:BIO, NIO, IO多路复用和AIO。
3.1 BIO
BIO即Blocking I/O(阻塞 I/O),BIO整个过程如下图:
程序发送请求给内核,然后由内核去进行通信,在内核准备好数据之前这个线程是被挂起的,所以在两个阶段程序都处于挂起状态。
3.2 NIO
NIO即Non-Blocking I/O(非阻塞 I/O), NIO整个过程如下图:
与BIO的明显区别是,发起第一次请求后,线程并没有被阻塞,它反复检查数据是否准备好,把原来大块不能用的阻塞时间分成了许多“小阻塞”(检查),所以进程不断有机会被执行。这个检查有没有准备好数据的过程有点类似于“轮询”。
3.3 IO多路复用
IO多路复用(I/O Multiplexing)有select,poll,epoll等不同方式,它的优点在于单个线程可以同时处理多个网络IO。
NIO中轮询操作是用户线程进行的,如果把这个任务交给其他线程,则用户线程就不用这么费劲的查询状态了。IO多路复用调用系统级别的select或poll模型,由系统进行监控IO状态。select轮询可以监控许多socket的IO请求,当有一个socket的数据准备好时就可以返回。
select: 注册事件由数组管理, 数组是有长度的, 32位机上限1024, 64位机上限2048。轮询查找时需要遍历数组。
poll:把select的数组采用链表实现,因此没了最大数量的限制
epoll方式:基于事件回调机制,回调时直接通知进程,无须使用某种方式来查看状态。
用户线程有一段时间是阻塞的,从上图来看,与NIO很像,但与NIO不一样的是,select不是等到所有数据准备好才返回,而是只要有一个准备好就返回,它的优势在于可以同时处理多个连接。若连接不是很多的话,它的效率不一定高,可能还会更差。
Java 1.4开始支持NIO(New IO),就是采用了这种方式,在套接字上提供selector选择机制,当发起select() 时会阻塞等待至少一个事件返回。
3.4 AIO
AIO即Asynchronous I/O(异步 I/O),这是Java 1.7引入的NIO 2.0中用到的。整个过程中,用户线程发起一个系统调用之后无须等待,可以处理别的事情。由操作系统等待接收内容,接收后把数据拷贝到用户进程中,最后通知用户程序已经可以使用数据了,两个阶段都是非阻塞的。AIO整个过程如下图:
AIO属于异步模型, 用户线程可以同时处理别的事情,我们怎么进一步加工处理结果呢? Java在这个模型中提供了两种方法:
一种是基于”回调”,我们可以实现CompletionHandler接口,在调用时把回调函数传递给对应的API即可
另一种是返回一个Future。处理完别的事情,可以通过isDone() 可查看是否已经准备好数据,通过get()方法等待返回数据。
3.5 小结
上面这几种模式,BIO整个过程都等待返回,NIO和IO多路复用在第二个阶段等待返回,因此从整个过程来看,这三个模式都属于同步方式。 AIO在整个过程中没有等待返回,属于异步方式。