阻塞与非阻塞IO
Java IO和NIO之间第一个最大的区别是,IO是面向流的,NIO是面向缓冲区的。
Java IO的各种流是阻塞的。这意味着,当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。
Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取立马返回,而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。 非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。
IO多路复用
多路复用就是通过复用同一个线程可以监听多个文件描述符fd,一旦某个文件描述符fd就绪(可以读/写),就通过(selector、poll、epoll)等方式通知用户进程进行相应read、write操作。最大优势是减少系统开销小,不必创建过多的进程/线程。
1、select
客户端操作服务器时会产生这三种文件描述符(简称fd):writefds(写)、readfds(读)、和exceptfds(异常)。select会阻塞住监视3类文件描述符,等有数据、可读、可写、出异常 或超时、就会返回,返回后通过遍历fdset整个集合来找到就绪的描述符fd,然后进行对应的IO操作。
优点:
几乎在所有的平台上支持,跨平台支持性好
缺点:
1、采用轮询方式扫描所有fd_set集合,会随着文件描述符FD数量增多而性能下降。
2、每次调用 select(),需要把fd_set集合从用户态拷贝到内核态
3、默认linux打开的fd文件描述符有限1024个,可修改宏定义,但是效率仍然慢。
时间复杂度: O(N)
数据结构: 存储结构bitmap
Java中使用Selector 的图解如下:
Seletor通过同时监听多个Channel,在系统内核没有响应的时候,Seletor通过select()方法把当前线程进入死循环当中,当有一个或多个Chanel就绪(如连接、接受、读或写)的时候,就开始进行处理。
注意,如果一个 Channel 要注册到 Selector 中,那么这个 Channel 必须是非阻塞的,即channel.configureBlocking(false);
因为 Channel 必须要是非阻塞的,因此 FileChannel 是不能够使用选择器的,因为 FileChannel 都是阻塞的.当像Selector注册Channel时,Channel.register()方法会返回一个SelectionKey 对象。这个对象代表了注册到该Selector的通道。可以通过selector.selectedKeys()方法访问这些SelectionKey对象。
可以遍历这个已选择的SelectionKey集合来访问就绪的通道
2、poll
基本原理与select一致,也是轮询+遍历;唯一的区别就是poll没有最大文件描述符限制,poll只有一个pollfd数组,数组中的每个元素都表示一个需要监听IO操作事件的文件描述符
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};
具体参考链接:https://man7.org/linux/man-pages/man2/poll.2.html
优点:
1、poll没有最大文件描述符限制
2、poll还有一个特点是“水平触发”,如果报告了fd后,没有被处理,那么下次poll时会再次报告该fd。
缺点:
1、采用轮询方式扫描所有pollfd集合,会随着文件描述符FD数量增多而性能下降。
2、每次调用 poll(),需要把pollfd集合从用户态拷贝到内核态
时间复杂度: O(N)
数据结构: 数组
3、epoll
没有fd个数限制,用户态拷贝到内核态只需要一次,使用事件通知机制来触发。通过epoll_ctl注册fd,一旦fd就绪就会通过callback回调机制来激活对应fd,进行相关的io操作。1G内存大概支持10万个句柄,这个数量和内存大小有关
epoll之所以高性能是得益于它的三个函数
1)epoll_create()系统启动时,在Linux内核里面申请一个B+树结构文件系统,返回epoll对象,也是一个fd,以及就绪链表(该链表存储已经就绪的文件描述符)
2)epoll_ctl() 每新建一个连接,都通过该函数操作epoll对象,在这个对象里面修改添加删除对应的链接fd, 绑定一个callback函数
3)epoll_wait() 轮训所有的callback集合,并完成对应的IO操作
优点:
1、没有文件描述符fd大小限制
2、通过事件驱动callback回调实现fd通知,而不是轮询的方式
时间复杂度: O(1)
数据结构: 红黑树
epoll有水平触发LT(level trigger)和边缘触发ET(edge trigger)两种触发模式,LT是默认的模式,ET是“高速”模式。
水平触发LT模式:当epoll_wait检测到描述符事件发生并将此事件通知用户应用程序,用户应用程序可以不立即处理该事件。下次调用epoll_wait时,会再次响应应用程序并通知此事件。
在边缘触发ET模式中,只会提示一次,当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件。如果不处理,下次调用epoll_wait时,不会再次响应应用程序并通知此事件。
IO多路复用的三种实现方式
select | poll | epoll | |
---|---|---|---|
数据结构 | bitmap | 数组 | 红黑树 |
最大连接数 | 1024 | 无限制 | 无限制 |
FD拷贝 | 每次调用select拷贝 | 每次调用poll拷贝 | fd首次调用epoll_ctl拷贝,每次调用epoll_wait不拷贝 |
工作效率 (时间复杂度) | 轮询:O(n) | 轮询:O(n) | 回调:O(1) |
参考:https://blog.csdn.net/weixin_44164489/article/details/108930663