并发原理:
几乎所有的IO接口都是阻塞型的,处理过程中线程将被阻塞,无法进行任何操作直到返回调用结果,或超时。
IO模型:系统内核 和 一个调用这个IO的线程
第一步 等待数据准备
第二步 将数据从内核拷贝到进程中
BIO 传统阻塞IO
用户线程发送IO请求(read操作)到系统内核,系统内核首先进行数据准备,然后进行数据拷贝。这两个过程中用户线程是完全阻塞的状态,啥也干不了。
用户线程发出IO请求,系统内核会开始准备数据并且直接返回一个error,然后用户线程接收到返回值可以非阻塞(干别的了)。然后用户线程不断地发送请求,时刻询问,如果系统内核没准备好就还是返回error,如果准备好就直接拷贝数据,拷贝过程中是阻塞的。
多路复用IO 也就是NIO,非阻塞IO
多了一个select,用来实时监听多个socket是否准备好。当任意socket准备好了 select就会返回,用户就会调用read,系统进行拷贝工作。
优点:select只占用单线程,不消耗太多资源,可以同时处理多个连接
异步IO 也就是AIO,异步IO
用户线程发出IO请求后,系统内核接收到请求会立刻给个返回,使用户线程不会阻塞。
然后系统内核自己等待数据准备,然后拷贝到用户内存,完成后通知一下用户线程。
举个实际场景的例子 分析一下什么是BIO,NIO,AIO?
BIO:小明坐在家里想打LOL,但是电脑正被他爹占着敲代码.. 这时候小明只能是阻塞的等待他爹改完bug 才能用电脑打游戏(没招)。小明就在他爹旁边干等着改BUG。
NIO,非阻塞IO: 小明觉得干等着太难受了,还耽误时间,就决定先去看电视 边看电视边等,看会电视回来问问他爹改完BUG了吗。小明非阻塞在他爹旁边
AIO,异步IO: 小明很生气,就坐客厅看电视去了,让他爹改好了BUG直接告诉他,省着他老来问了。异步IO
非阻塞 IO 的核心在于使用一个 Selector 来管理多个通道,可以是 SocketChannel,也可以是 ServerSocketChannel,将各个通道注册到 Selector 上,指定监听的事件。
之后可以只用一个线程来轮询这个 Selector,看看上面是否有通道是准备好的,当通道准备好可读或可写,然后才去开始真正的读写,这样速度就很快了。我们就完全没有必要给每个通道都起一个线程。
NIO 中 Selector 是对底层操作系统实现的一个抽象,管理通道状态其实都是底层系统实现的,这里简单介绍下在不同系统下的实现。
select:上世纪 80 年代就实现了,它支持注册 FD_SETSIZE(1024) 个 socket,在那个年代肯定是够用的,不过现在嘛,肯定是不行了。
poll:1997 年,出现了 poll 作为 select 的替代者,最大的区别就是,poll 不再限制 socket 数量。
select 和 poll 都有一个共同的问题,那就是它们都只会告诉你有几个通道准备好了,但是不会告诉你具体是哪几个通道。所以,一旦知道有通道准备好以后,自己还是需要进行一次扫描,显然这个不太好,通道少的时候还行,一旦通道的数量是几十万个以上的时候,扫描一次的时间都很可观了,时间复杂度 O(n)。所以,后来才催生了以下实现。
epoll:2002 年随 Linux 内核 2.5.44 发布,epoll 能直接返回具体的准备好的通道,时间复杂度 O(1)。
CPU从磁盘数据读取过程 https://www.cnblogs.com/ttaall/p/13738562.html
传统IO与零拷贝的几种实现 https://www.cnblogs.com/ttaall/p/14029974.html
图解计算机底层IO过程及JavaNIO https://www.cnblogs.com/ttaall/p/14128788.html