JavaIO
BIO
PushBackInputStream 是一个处理流,它内部维护了一个缓冲数组buf
,在读入字节的过程中可以将读取到的字节数据回退给缓冲区中保存,下次可以再次从缓冲区中读出该字节数据。所以 PushBackInputStream 允许多次读取输入流的字节数据,只要将读到的字节放回缓冲区即可。传统的流读顺序读取数据后会进行丢弃,PushBackInputStream 读入数据后会在内存的数组buf缓存一段时间,因此可以用于 对读取到数据分类处理,可以让一个线程专门读取数据,其他线程处理缓冲区的数据
NIO
Java NIO有三大组成部分:Buffer,Channel,Selector,通过事件驱动模式实现了什么时候有数据可读的问题。
Channel可以阻塞也可以非阻塞,与Selector使用Channel必须处于非阻塞模式(FileChannel不能切换到非阻塞模式。而套接字通道都可以),调用Selector.wakeup()方法即可。阻塞在select()方法上的线程会立马返回,如果有其它线程调用了wakeup()方法,但当前没有线程阻塞在select()方法上,下个调用select()方法的线程会立即“醒来(wake up)”。
在select上没有返回则会阻塞,但是会有空轮询的bug问题,即:有可能没有返回也会继续轮询,在netty中解决
每完成一次select操作进行一次计数,若在循环周期内 发生N次空轮询,如果N值大于BUG阈值(默认为512),就进行空轮询BUG处理。重建Selector,判断是否是其他线程发起的重建请求,若不是则将原SocketChannel从旧的Selector上去除注册,重新注册到新的 Selector上,并将原来的Selector关闭。
操作系统层面 select 函数将所有fd存储为一个bitmap(最大1024个fd)传输的内核态进行轮询检查,当fd处于就绪状态,则会进行设置一个标志位,最终函数返回就绪状态的fd数量,函数调用者发现有就绪状态的fd就会去操作系统遍历查找就绪状态的fd;poll方式是将bitmap换成了链表(去除1024限制) epoll解决函数调用参数拷贝问题和返回不知道哪些socket就绪;在内核空间创建epollevent对象,返回对象(相当于内核开辟空间,并且知道这块位置),一块存储监听socket列表,一块存储 就绪列表;返回0没有就绪,大于零有几个就绪,-1异常,epoll_wait的参数epoll_event,将就绪信息拷贝到这个对象中,就可以拿到就绪列表;保存event_poll对象用红黑树,查找复杂度O(lgN),监听的socket数量是linux最大打开文件数(因为fd是一个虚拟文件)
epoll_ctl:根据eventpoll-id增删改查(维护)eventpoll对象的检查列表(监听socket列表)
epoll_wait:参数是监听的eventpoll对象集合,阻塞集合中的对象,知道某个socket就绪才返回
socket对象有:读缓冲区,写缓冲区,等待队列
epoll_ctl添加到socket到监听列表,实际上是添加到等待队列,当网络传输来的数据从网卡通过DMA写道内存,通过中断将socket追加到就绪队列尾部
AIO
通过观察者模式回调调用,但是会有 背压 问题,即:生产者速度比消费者速度快很多,导致无法接收数据问题
背压问题,可以通过1.过滤部分请求;2.缓存部分请求;3.响应式拉取(回调),即:在每次回调中处理完数据通知生产下一次数据
异步IO需要系统层面的支持,目前 Windows 下通过 IOCP 实现了真正的异步IO,而在 Linux 系统下的 AIO 并不完善。