关注的是被调用方的执行方式及返回时机。
同步:被调用方做完内部所有事情后再返回,同步调用的调用者一定会得到被调用方的结果。
异步:被调用方先返回,再做事情,做完所有事情后再通知调用方(回调方法),异步调用方不会立即得到被调用方的结果,而是当被调用方执行完成后通过回调函数处理该结果。
关注的是调用方在被调用方返回结果之前的这段时间,是否处于等待状态。
阻塞:调用方在等待被调用方返回结果的这段时间什么都不干;
非阻塞:调用方在等待被调用方返回结果的这段时间去处理其他事情。
同步/异步从行为角度描述事物,针对的对象是被调用方,主要关注被调用方的执行方式及返回时机。
阻塞/非阻塞描述当前事物的状态(等待调用结果时的状态),针对的对象是调用方,主要关注调用方是否在等待被调用方返回结果的这段时间是否处于等待状态。
并发:一个时间段内,几个程序都在同一个CPU上运行,但任意一个时刻点上只有一个程序在处理机上运行。
并行:一个时间段内,几个程序在不同的CPU上运行,任意一个时刻点上,有多个程序在同时运行,并且多道程序之间互不干扰。
I/O是Input输入/Output输出的简称,通常指数据在内部存储器(内存)
和外部存储器(硬盘、优盘)
或其他周边设备之间的输入和输出。
输入/输出是信息处理系统(计算机)与外部世界(人类或另一信息处理系统)之间的通信。
BIO (blocking-io,同步阻塞I/O)模式,数据的读取写入必须阻塞在一个线程内等待其完成。
采用BIO通信模型的服务端,一般由一个独立的Acceptor线程负责监听客户端的连接,一般通过while(true)循环中服务端会调用accept()方法等待接收客户端的连接的方式监听请求;一旦接收到一个客户端的连接请求,就可以建立通信套接字在这个通信套接字上进行读写操作,此时当前线程不能再接收其他客户端连接请求,只能等待当前客户端的操作执行完成(可通过多线程来支持多个客户端的连接)
一请求一应答通信模型:服务端接收到客户端连接请求后为每个客户端创建一个新的线程进行链路处理,处理完成后,通过输出流返回应答给客户端,线程销毁。
客户端并发访问量增加后一请求一应答模型的弊端:若连接不做任何事情就会造成不必要的线程开销,在Java虚拟机中,线程是宝贵的资源,线程的创建、销毁、切换成本很高,如果并发量增加会导致线程数急剧膨胀可能会导致线程堆栈溢出、创建新线程失败等问题,最终导致服务端不能对外提供服务。
解决一请求一应答模型的弊端,后端通过一个线程池来处理多个客户端的请求接入,形成客户端个数M:线程池最大线程数N的比例关系,M可以远大于N,通过线程池就可以灵活的调配线程资源,避免每个请求都创建一个独立线程造成的线程资源耗尽,并设置线程池最大值,防止由于海量并发接入导致线程耗尽。
当活动连接数不是特别高(小于1000),BIO模型比较实用,每个连接专注于自己的I/O且编程模型简单,线程池本身是个天然的漏斗,可以缓冲一些系统处理不了的连接或请求,当若面对上百万级连接时,BIO无能为力。
同步非阻塞的I/O模型,支持面向缓冲的,基于通道的I/O操作方法,提供SocketChannel和ServerSocketChannel并支持阻塞(与BIO一样)与非阻塞两种模式。对于低负载、低并发的应用程序可以使用同步阻塞I/O提升开发速率和更好的维护性;对于高负载、高并发的(网络)应用使用NIO的非阻塞模式。
jdk提供的nio的代码在:java.nio包下。
IO:IO各种流是阻塞的,当线程调用read()或write()时,该线程被阻塞,直到一些数据被读取或数据完全写入,该线程在此期间不能再干任何事情了。
NIO:NIO可以进行非阻塞IO操作,比如:单线程从通道读取数据到buffer,同时继续做别的事情,当数据读取到buffer后,线程再继续处理数据。写数据也是一样的,一个线程请求写入一些数据到某通道,不需要等待它完全写入,这个线程同时可以去做别的事情。
IO:面向流的IO可以将数据直接写入或将数据直接读到Stream对象中。
NIO:NIO直接读到Buffer中进行操作,所有数据都是用缓冲区处理的,读取数据是直接读到缓冲区,写入数据也是直接写入到缓冲区,任何时候访问NIO中的数据,都是通过缓冲区进行操作。
NIO通过Channel(通道)进行读写
Channel通道是双向的,可读也可写,而流是单向的。无论读写,通道只能与Buffer交互,通道可以异步的读写。
Selector选择器用于使用单个线程处理多个通道,只需要较少的线程来处理这些通道,线程之间切换对于操作系统来讲是昂贵的,因此选择器会提高系统效率。
Thread->Selector->Channel1/Channel2/Channel3
NIO(多selector)模型图:
NIO中所有的IO都是从Channel(通道)就开始的
从通道进行数据读取:创建一个缓冲区,然后请求通道读取数据->(Channel->Buffer)
从通道进行数据写入:创建一个缓冲区,填充数据,并要求通道写入数据->(Buffer->Channel)
Channel是一个对象,可以通过它读取和写入数据。拿 NIO 与原来的 I/O 做个比较,通道就像是流,而且他们面向缓冲区的。
所有数据都通过 Buffer 对象来处理。您永远不会将字节直接写入通道中,相反,您是将数据写入包含一个或者多个字节的缓冲区。同样,您不会直接从通道中读取字节,而是将数据从通道读入缓冲区,再从缓冲区获取这个字节。
通道与流的不同之处在于通道是双向的。而流只是在一个方向上移动(一个流必须是 InputStream 或者 OutputStream 的子类), 而 通道 可以用于读、写或者同时用于读写。
因为它们是双向的,所以通道可以比流更好地反映底层操作系统的真实情况。特别是在 UNIX 模型中,底层操作系统通道是双向的。
缓冲区 是一个固定数据量的指定基本类型的数据容器。除内容之外,缓冲区还具有位置 和界限,其中位置是要读写的下一个元素的索引,界限是第一个应该读写的元素的索引。基本 Buffer 类定义了这些属性以及清除、反转 和重绕 方法,用以标记 当前位置,以及将当前位置重置 为前一个标记处。
每个非布尔基本类型都有一个缓冲区类。每个类定义了一系列用于将数据移出或移入缓冲区的 get 和 put 方法,用于压缩、复制 和切片 缓冲区的方法,以及用于分的异类或同类二进制数据序列),访问要么是以 big-endian字节顺序进行,要么是以 little-endian 字节顺序进行。
AIO是异步IO,在读取数据的整个过程都是异步的,当数据准备好之后,直接由操作系统内核缓冲区将数据复制到用户缓冲区,整个过程不需要用户线程做什么。
但AIO现在还没有广泛应用,现在的大公司服务端采用的都是多路复用IO模型(NIO),特点是支持高并发。