当前位置: 首页 > news >正文

Java学习 ------BIO模型

        在 Java 的 I/O 模型中,BIO(Blocking I/O,阻塞式 I/O)是最基础也最经典的一种。它伴随着 Java 的诞生而出现,虽然在高并发场景下逐渐被 NIO 和 AIO 取代,但作为理解其他 I/O 模型的基础,其原理和机制仍具有重要的学习价值。

        Java BIO 的核心特点是阻塞,即当一个 I/O 操作执行时,对应的线程会被阻塞,直到该操作完成才能继续执行其他任务。其工作原理可以概括为 “一个连接一个线程”,具体流程如下:​

(1)建立连接阶段:服务器端通过ServerSocket监听指定端口,当客户端通过Socket发起连接请求时,服务器端会调用accept()方法接受连接。在这个过程中,accept()方法是阻塞的,也就是说,在没有客户端连接时,服务器端的线程会一直停留在accept()方法处等待。​

(2)数据传输阶段:连接建立后,服务器端和客户端之间通过输入流和输出流进行数据传输。此时,无论是调用输入流的read()方法读取数据,还是调用输出流的write()方法写入数据,线程都会被阻塞。例如,当read()方法被调用时,如果此时没有数据可读,线程会一直阻塞,直到有数据到来或者连接关闭。​

(3)线程模型:在 BIO 模型中,每一个客户端连接都需要一个独立的线程来处理。服务器端会为每个新接入的客户端创建一个线程,该线程负责处理与该客户端的所有 I/O 操作。当客户端的 I/O 操作完成(如数据传输结束、连接关闭)后,该线程才会被释放。​

        BIO作为一款老I/O模型,其也有不少优点​,例如

(1)实现简单易懂:BIO 的编程模型非常直观,API 使用简单,开发者不需要关注复杂的底层机制,容易上手和理解。对于一些简单的网络应用或本地文件操作,使用 BIO 可以快速开发出功能。​

(2)适合简单场景:在并发量小、连接数少的场景下,BIO 能够稳定工作,且性能开销相对可控。例如,一些小型的客户端 - 服务器应用,或者本地的文件读写操作,BIO 是一个不错的选择。​

(3)兼容性好:作为 Java 最早的 I/O 模型,BIO 在各种 Java 版本中都能很好地兼容,不存在版本适配问题,对于一些老旧系统的维护和开发非常友好。​

        但同样的,其缺点也不少:​

(1)阻塞导致效率低:BIO 的最大问题在于阻塞。当一个线程在进行 I/O 操作时,会一直处于阻塞状态,在此期间不能做任何其他事情,造成了线程资源的浪费。例如,当服务器端的一个线程在等待客户端发送数据时,这个线程就一直闲置,无法处理其他客户端的请求。​

(2)高并发下性能瓶颈明显:由于每一个客户端连接都需要一个独立的线程处理,在高并发场景下,会创建大量的线程。而线程的创建和销毁需要消耗大量的系统资源,同时线程之间的上下文切换也会带来额外的开销,容易导致系统性能急剧下降,甚至出现崩溃。​

(3)资源利用率低:大量的阻塞线程会占用大量的内存资源和 CPU 时间片,使得系统的资源利用率很低。在极端情况下,可能会因为线程数量过多而导致 OutOfMemoryError 错误。​

        下面通过一个简单的客户端 - 服务器通信示例来展示 Java BIO 的使用方法。​

服务器端代码​

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;public class BioServer {public static void main(String[] args) {ServerSocket serverSocket = null;try {// 创建ServerSocket,监听指定端口serverSocket = new ServerSocket(8888);System.out.println("服务器启动,监听端口8888...");while (true) {// 阻塞等待客户端连接Socket socket = serverSocket.accept();System.out.println("收到新的客户端连接:" + socket.getInetAddress().getHostAddress());// 为每个客户端连接创建一个新的线程进行处理new Thread(() -> {try (InputStream in = socket.getInputStream();OutputStream out = socket.getOutputStream()) {byte[] buffer = new byte[1024];int len;// 阻塞读取客户端发送的数据while ((len = in.read(buffer)) != -1) {String message = new String(buffer, 0, len);System.out.println("收到客户端消息:" + message);// 向客户端发送响应数据String response = "服务器已收到消息:" + message;out.write(response.getBytes());out.flush();}} catch (IOException e) {e.printStackTrace();} finally {try {socket.close();System.out.println("客户端连接关闭");} catch (IOException e) {e.printStackTrace();}}}).start();}} catch (IOException e) {e.printStackTrace();} finally {if (serverSocket != null) {try {serverSocket.close();} catch (IOException e) {e.printStackTrace();}}}}
}
/*首先创建ServerSocket并绑定到 8888 端口,然后进入一个无限循环,通过accept()方法阻塞等待客户端的连接。当有客户端连接时,创建一个新的线程来处理该客户端的请求。在新线程中,通过输入流读取客户端发送的数据,处理后通过输出流向客户端发送响应,最后关闭连接。
*/

客户端代码​

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;public class BioClient {public static void main(String[] args) {Socket socket = null;try {// 连接服务器socket = new Socket("localhost", 8888);System.out.println("已连接到服务器");// 获取输入流和输出流OutputStream out = socket.getOutputStream();InputStream in = socket.getInputStream();// 向服务器发送数据Scanner scanner = new Scanner(System.in);System.out.println("请输入要发送的消息:");String message = scanner.nextLine();out.write(message.getBytes());out.flush();// 读取服务器的响应byte[] buffer = new byte[1024];int len = in.read(buffer);String response = new String(buffer, 0, len);System.out.println("收到服务器响应:" + response);scanner.close();} catch (IOException e) {e.printStackTrace();} finally {if (socket != null) {try {socket.close();System.out.println("客户端连接关闭");} catch (IOException e) {e.printStackTrace();}}}}
}
/*
创建Socket并连接到服务器的 8888 端口,然后通过输出流向服务器发送数据,再通过输入流读取服务器的响应,最后关闭连接。
*/

        从代码中可以清晰地看到 BIO 的阻塞特性:accept()方法会阻塞等待客户端连接,read()方法会阻塞等待数据到来。同时,每一个客户端连接都对应一个独立的线程,这也体现了 BIO “一个连接一个线程” 的线程模型。​

http://www.lryc.cn/news/595579.html

相关文章:

  • 【VASP】VASP 机器学习力场(MLFF)实战
  • C++ <继承> 详解
  • js迭代器
  • JAVA序列化知识小结
  • 我国《数字中国规划》对虚拟产权的监管:合规框架下的渐进式创新
  • stream event
  • 前端,demo操作,增删改查,to do list小项目
  • C++ 分配内存释放内存
  • Anaconda 路径精简后暴露 python 及工具到环境变量的配置记录 [二]
  • 【C#】C# 事件 两次 -= 会怎么样?
  • C# 结构体
  • C# 转换(is和as运算符)
  • XSS学习总结
  • Unreal ARPG笔记
  • 《画布角色的双重灵魂:解析Canvas小游戏中动画与碰撞的共生逻辑》
  • Spring Boot注解详解
  • 影刀 RPA:批量修改 Word 文档格式,高效便捷省时省力
  • 通俗易懂卷积神经网络(CNN)指南
  • 海康威视视觉算法岗位30问及详解
  • 多片RFSoC同步,64T 64R
  • STM32小实验四--按键控制LED灯
  • Neo4j 5.x版本的导出与导入数据库
  • 车载软件架构 --- 软件开发面临的问题
  • DAY17 常见聚类算法
  • Spring AI 集成阿里云百炼与 RAG 知识库,实现专属智能助手(框架思路)
  • SpringSecurity 详细介绍(认证和授权)
  • 广东省省考备考(第五十二天7.21)——数量、判断推理(听课后强化训练)
  • 【qml-3】qml与c++交互第二次尝试(类型方式)
  • Android MTK平台预置多张静态壁纸
  • LinkedList与链表(单向)(Java实现)