1. BIO(Blocking I/O)
1. 1 BIO(Blocking I/O)模型概述
BIO,即“阻塞I/O”(Blocking I/O),是一种同步阻塞的I/O模式。它的主要特点是,当程序发起I/O请求(比如读取数据或写入数据)时,程序会一直等到这个请求完成,才继续往下执行。在BIO模型下,每个连接都需要一个独立的线程去处理。
举个简单的例子:假设你写了一个服务器,它会接收客户端的连接,并与客户端进行数据交流。在BIO模式下,这个服务器每接收一个连接,就需要新创建一个线程来处理该连接的数据读写。这意味着:
- 如果有100个客户端同时连接服务器,服务器需要创建100个线程来处理。
- 如果客户端不发送数据,线程就会空闲等待,但依旧会占用系统资源(例如CPU和内存)。
这种模型对于连接数量较少且连接时间较短的场景比较合适。但是如果连接数量增多,比如成千上万个连接,BIO模式就显得捉襟见肘了,因为线程数量会迅速增加,系统资源会被大量占用,最终导致性能下降。
1.2BIO的工作原理
在BIO(Blocking I/O)模式中,关键的机制就是“阻塞”。简单来说,阻塞意味着当一个I/O操作(例如读取数据)在进行时,程序会“停下来”直到操作完成才能继续。让我们一步步来看一个BIO服务器的典型工作流程:
-
等待客户端连接:服务器启动后,会在某个端口上等待客户端的连接请求。
-
接收连接:当客户端发起连接时,服务器会接收该连接,并创建一个新的线程来处理这个连接。这个线程负责处理客户端发送过来的所有数据,直到连接关闭。
-
数据读取阻塞:一旦线程开始读取数据,它会进入阻塞状态,直到收到完整的数据或遇到异常。在这期间,线程不能做其他事情。想象成排队买票,一个窗口只能接待一个人,其他人都得等前一个人买完才能上前。
-
处理数据:当线程收到数据后,它会处理这些数据(例如解析请求、生成响应)。
-
发送数据阻塞:处理完数据后,线程会将响应发送给客户端。在发送过程中,线程也会被阻塞,直到数据全部发送完毕。
-
结束连接或继续等待:如果客户端断开连接,线程会关闭。如果客户端保持连接,线程会继续等待下一个数据块的到来。
整个流程看似简单,但每一个连接都占用一个线程。所以,一旦连接数较多,系统资源就会迅速消耗,产生以下问题:
1.3 BIO的优缺点分析
优点
-
逻辑简单:BIO模型结构直观,每个线程专门服务一个客户端连接,代码编写和调试相对简单。
-
适合短连接:对于连接时间较短、交互少的场景(例如Web应用中的HTTP请求),BIO模型能胜任。
缺点
-
资源占用高:由于每个连接都需要一个线程来处理,线程数量直接与连接数成正比,导致资源消耗随连接数增加而大幅度提高。
-
性能瓶颈:线程之间需要竞争CPU时间片,同时可能会导致大量线程进入等待状态(因为阻塞I/O),从而降低CPU的整体利用率。
-
扩展性差:BIO模型并不适合需要处理大量并发连接的应用场景,比如实时聊天系统或大规模服务器。因为线程数受限于系统资源,过多的线程会拖慢系统响应速度,甚至导致崩溃。
2. NIO(Non-blocking I/O)
NIO,全称非阻塞I/O(Non-blocking I/O),是Java在1.4版引入的一种新I/O模型,它通过非阻塞机制和多路复用(Selector)来管理大量的连接,目标是提高高并发下的性能。我们分步来看看NIO的特点和工作流程。
2.1 NIO的关键概念
-
非阻塞(Non-blocking):NIO允许线程在等待I/O操作时不用一直被“卡住”。比如,线程可以发起一个读取请求并立即返回,如果数据未准备好,线程可以先去做其他事,不会阻塞等待。
-
多路复用(Selector):NIO通过一个
Selector
来管理多个通道(Channel)和连接。Selector
是一个事件管理器,能在同一线程中监控多个连接的状态,只有当某个连接有数据准备好时才处理该连接。这使得一个线程可以处理多个连接,节省了大量的资源。
2.2 NIO的核心组件
在NIO中,有几个核心组件我们需要理解:
-
Channel(通道):类似于BIO中的“流”,但
Channel
是双向的,即既能读又能写。常用的Channel
包括SocketChannel
(用于网络数据传输)、FileChannel
(用于文件操作)等。 -
Buffer&