Skip to content

BIO

一、重要概念解析

1. 阻塞 IO 与 非阻塞 IO
  • 定义:程序级别的概念,描述程序在请求操作系统执行 IO 操作后,若 IO 资源未准备好时的处理方式。
  • 阻塞 IO
    • 程序等待,直到 IO 资源准备好并完成操作。
    • 示例:调用 read() 时,若数据未就绪,线程暂停。
  • 非阻塞 IO
    • 程序不等待,立即返回(通常返回错误码),通过轮询检查 IO 是否完成。
    • 示例:线程反复调用 read() 检查数据状态。
  • 关键区别:阻塞等待 vs 非阻塞轮询。
2. 同步 IO 与 异步 IO
  • 定义:操作系统级别的概念,描述操作系统在收到 IO 请求后,若资源未就绪时的响应方式。
  • 同步 IO
    • 操作系统不立即响应,程序需等待资源就绪。
    • 示例:accept() 或 read() 阻塞至数据到达。
  • 异步 IO
    • 操作系统立即返回标记(如文件描述符),资源就绪后通过事件机制通知程序。
    • 示例:aio_read() 返回后,数据准备好时通知。
  • 关键区别:同步需等待 vs 异步立即返回并回调。

二、传统 BIO 通信方式简介

  • 定义:传统阻塞式 IO(BIO,Blocking IO)通信方式。
  • 特点
    • 客户端:发出请求后等待服务器响应,无法执行其他任务。
    • 服务器端:单线程处理请求时,其他客户端请求需排队等待。
  • 典型场景
    • 客户端 A 请求,服务器处理 A 时,客户端 B 请求被阻塞。
BIO 的问题
  • 并发性差
    • 服务器单线程一次只能处理一个客户端请求。
    • 高并发下,客户端请求排队严重影响效率。
  • 资源限制
    • 单线程无法应对多个同时到达的请求。

三、多线程方式(伪异步方式)

  • 改进思路
    • 服务器端:主线程接收请求后,将业务处理交给独立线程,主线程继续接收其他请求。
    • 客户端:使用子线程与服务器通信,主线程不受阻塞,子线程通过监听/观察模式获取响应。
  • 优点
    • 客户端主线程可继续工作。
    • 服务器端业务处理并行化。
多线程的局限性
  1. 接受请求仍阻塞
    • accept() 仍为单线程顺序执行,无法并行接收多个客户端请求。
    • 业务处理虽多线程化,但数据接收仍串行。
  2. 线程资源限制
    • Linux 下线程数有限(查看:cat /proc/sys/kernel/threads-max)。
    • 线程切换开销随数量增加而增大,CPU 用于业务的时间减少。
  3. 资源消耗
    • JVM 创建线程需分配堆栈空间(默认 128K,可通过 -Xss 调整)。
    • 线程池虽缓解创建开销,但任务积压仍消耗内存。
  4. 长连接问题

    • 长连接下线程不释放,资源占用失控。
  5. 结论:多线程仅优化业务处理,未解决 accept() 和 read() 的阻塞问题,不是高并发的根本解决方案。

四、BIO 通信方式深入分析

问题根源
  • 核心:BIO 的阻塞性源于 accept() 和 read() 的操作系统级同步 IO 实现,而非是否使用多线程。
  • 验证方法
    • 模拟 20 个客户端并发请求,观察服务器单线程和多线程的表现。
实验代码分析
  1. 客户端代码
    • SocketClientDaemon:启动 20 个线程模拟客户端。
    • SocketClientRequestThread:使用 CountDownLatch 确保 20 个客户端同时发送请求。
    • 行为:连接服务器,发送请求,等待响应。
  2. 单线程服务器(SocketServer1)
    • 使用 serverSocket.accept() 接收请求,顺序处理。
    • 日志显示:请求逐一被接收和处理。
  3. 多线程服务器(SocketServer2)
    • 主线程 accept() 接收请求,创建线程处理业务。
    • 日志显示:accept() 仍顺序执行,业务处理并行。
实验结果
  • 单线程:20 个请求按序处理,效率低。
  • 多线程
    • 接收仍串行(accept() 阻塞)。
    • 处理并行,但未解决根本阻塞问题。

五、阻塞原因解析

  • 操作系统支持
    • accept() 和 read() 的阻塞性依赖操作系统同步 IO 实现。
  • 工作原理
    • 服务器调用 accept(),询问操作系统是否有新连接。
    • 若无连接,操作系统等待,accept() 阻塞。
    • ServerSocket.accept():监听连接并接受,阻塞直到连接建立。
    • 操作系统未返回数据,线程挂起等待。

六、总结

  • BIO 核心问题
    • accept() 和 read() 的阻塞性限制了并发能力。
  • 多线程局限
    • 仅优化业务处理,接收仍串行,资源消耗大。
  • 解决方向
    • 异步 IO 或 IO 多路复用(如 NIO)可突破阻塞限制。