NIO

2025/2/251387 字

虽然说是java的Nio,但是实际上我这篇文章要讲的主要是操作系统层面上的NIO

之所以是这个标题还有是在java的笔记中是因为我是在学Java的的时候接触到这个概念的。

下面进入正题

了解NIO(非阻塞IO)之前,我们先了解一下传统的BIO(阻塞IO)

BIO(或者叫OIO)

传统的,最简单的进行io操作的方式,也就是当应用程序调用read或write等方法时,调用方法的线程进入阻塞状态,一直到io操作完成

这种方式有个明显的缺点就是,在网络io中,一个线程只能建立一个连接,因为需要通过read阻塞的方式时刻准备接收用户的请求。

那么一旦我们的系统需要同时处理大量请求,就需要同时维持同样数量的线程。

优点: 线程阻塞时, 消耗的CPU资源小
缺点: 高并发时容易耗尽线程资源, 且大量线程会引起线程切换的巨大开销, 造成响应不及时


NIO

引用一段话

  1. 应用B响应速度很快,在应用A读取TCP缓冲区时,就已经把响应数据返回了。那么应用A就可以顺利获取到数据,皆大欢喜。
  2. 应用B响应比较慢,在应用A读取TCP缓冲区时,还没有将响应数据返回了。这个时候操作系统就面临两种选择:
    选择一:把应用A的本次调用阻塞在这,等到应用B的数据达到了缓冲区再重新唤醒应用A。
    选择二:告诉应用A你要的数据还没来,你等会再来问。

如果说BIO是第一种选择,那么NIO就是第二种选择,当响应端来不及发送响应时,先发送一个数据未准备的信息回去,这样请求端收到后就可以时不时进行轮询,不断主动询问数据准备好没有。

在轮询的间隙线程不是阻塞的,可以做别的事情,所以叫非阻塞IO

NIO, 靠着Linux提供的可以立即返回的read(), 在第一步中是不阻塞的, 在第二步中仍然是阻塞的.

优点: 把应用线程的read调用, 分割出了第一步和第二步, 应用线程可以做更多实时的控制
缺点: 第一步中的轮询, 大量占用CPU时间, 降低系统资源利用率, 所以一般web服务器也不使用这样的NIO

两种方式都不尽如人意,那有没有同时结合两者优点的方式:既不用大量轮询,也不用同时开启大量线程还能同时处理多个请求的方式呢?

我们直接让一个线程接收所有的请求,然后在没有请求的时候就阻塞等待不就行了吗☝️🤓💡

但是如何让一个线程接收通过socket的请求呢🤔(这里以网络举例,以文件举例的话是如何让一个线程读写多个文件)

这就需要操作系统的支持

下面介绍操作系统的IO多路复用

IO多路复用

IO多路复用是一种同步IO模型, 是操作系统提供的能力, 使用少量线程监听Linux的多个IO文件描述符, 也就是线程复用于多个IO请求.
如果有任意IO文件描述符就绪, 就会告知应用进行读取,
如果没有IO文件描述符就绪, 就会阻塞应用线程.
Linux中, IO多路复用的实现是select/poll/epoll

IO多路复用的作用 : 线程复用于多个请求, 用较小的线程开销来支持更多的并发请求

NIO结合IO多路复用后, select/poll/epoll替代了轮询.

下面细讲一下这三种方式的不同

select/poll/epoll

Linux上的IO多路复用演变经历了3个阶段:

  1. select
    轮询遍历IO文件描述符, O(n)的线性时间复杂度, 因此随着IO文件描述符的增多, 性能下降
    单进程能够打开的文件描述符有数量限制, 导致应用的最大连接数是1024
    调用select时, 还需要文件描述符在用户空间和内核空间的拷贝, 影响性能
  2. poll
    同样是轮询遍历IO文件描述符, O(n)的线性时间复杂度, 因此随着IO文件描述符的增多, 性能下降
    无文件描述符的数量限制
    调用poll时, 还需要文件描述符在用户空间和内核空间的拷贝, 影响性能
  3. epoll
    解决了select和poll的全部缺点,
    采用注册回调机制, 替代了轮询遍历,
    无文件描述符的数量限制
    利用mmap减少了文件描述符在用户空间和内核空间的拷贝
    因此性能高

以上就是现在使用的NIO的机制讲解(用法这里不讲,因为我还没有实践过,后面实践了补上)

AIO

都讲到这里了,感觉NIO的机制还是不够现代,我们接下来继续讲解一下现在用得最多的AIO的机制。

💬 评论区

写评论

暂无评论,来发表第一条吧。

© 2026 Corn 的自留地