线程阻塞的理解


日常业务(Spring Boot Web、微服务、数据库、消息队列、文件导出等),通常会有线程执行 IO 阻塞业务场景,每个场景都会配Java/Spring Boot 实际代码示例,并说明为什么会阻塞、虚拟线程如何优化


一、核心结论

IO 阻塞:线程发起「输入 / 输出操作」后,必须等待操作完成才能继续执行,期间线程 idle(空闲),平台线程会被操作系统挂起,造成资源浪费。虚拟线程的价值:遇到这些阻塞时,JVM 会自动解绑平台线程,把平台线程分配给其他虚拟线程,极大提升吞吐量。


二、最常见的 IO 阻塞场景(Java 开发高频)

网络 IO 阻塞(Web 开发占比 80%)

场景 1:同步 HTTP 接口调用

调用第三方接口、内部微服务接口(RestTemplate/OkHttp 同步调用)

// 线程会阻塞在这里,等待第三方接口返回结果
String result = restTemplate.getForObject("https://api.xxx.com/user", String.class);

阻塞原因:等待网络响应

虚拟线程优化:阻塞时释放平台线程

场景 2:数据库 JDBC 操作(MySQL/PostgreSQL/Oracle)

所有基于 JDBC 同步的数据库查询 / 插入 / 更新(MyBatis、JPA、JdbcTemplate)

// 线程阻塞:等待数据库返回查询结果
List<Order> orders = orderMapper.selectList(queryWrapper);

阻塞原因:等待数据库网络 IO + 磁盘 IO

虚拟线程优化:海量请求下,不再因为数据库阻塞占用大量平台线程

场景 3:Redis / 缓存中间件操作

Spring Data Redis 同步操作

// 线程阻塞:等待Redis返回数据
String userInfo = redisTemplate.opsForValue().get("user:1001");

场景 4:消息队列同步操作(Kafka/RabbitMQ)

结合你之前问的 Kafka 导出文件 场景:

// 发送消息时的阻塞等待、消费消息的阻塞处理
kafkaTemplate.send("export-task-topic", msg).get();

场景 5:对象存储上传 / 下载(MinIO/OSS)

你之前配置的 MinIO 文件上传、导出文件下载:

// 阻塞:等待文件上传到MinIO完成
minioTemplate.upload(bucketName, fileName, inputStream);

场景 6:微服务同步调用(OpenFeign/Dubbo 同步)

// 阻塞:等待订单服务返回结果
OrderDTO order = orderFeignService.getOrderById(1001);

本地文件 IO 阻塞(磁盘 IO)

所有本地文件读写、大文件导出 / 导入(你之前的订单 / 用户导出场景)

场景 1:文件读写(Files/FileInputStream)

// 阻塞:等待磁盘写入/读取完成
Files.write(Paths.get("order.csv"), csvData.getBytes());

场景 2:大文件导出(Excel/CSV)

// 导出订单时,流式写入文件,全程阻塞IO
try (CSVWriter writer = new CSVWriter(new FileWriter("user_export.csv"))) {
    writer.writeAll(userList); // 阻塞操作
}

阻塞式等待操作(归为 IO 类阻塞,虚拟线程自动优化)

场景 1:线程休眠 Thread.sleep()

模拟阻塞、延时处理:

// 线程强制休眠,属于显式阻塞
Thread.sleep(1000);

场景 2:同步锁等待

synchronizedReentrantLock 等待锁释放:

synchronized (this) {
    // 线程阻塞:等待其他线程释放锁
}

底层网络 Socket 阻塞

原生 Java Socket 编程、TCP 通信:

Socket socket = new Socket("127.0.0.1", 8080);
// 阻塞:等待客户端连接/数据传输
InputStream in = socket.getInputStream();
in.read(buffer); // 阻塞IO

三、这些场景的共同特征(判断标准)

只要代码满足下面任意一条,就是 IO 阻塞场景

  1. 代码执行后必须等待外部响应(数据库 / 接口 / Redis / 文件)
  2. 线程不消耗 CPU,处于 idle 等待状态
  3. 平台线程会被操作系统挂起,资源浪费

四、虚拟线程的黄金优化场景(重点)

Spring Boot 3.2+ 开启虚拟线程后,以下业务收益最大

  1. IO 密集型接口(查询数据库、调用第三方接口、Redis 操作)
  2. 文件导出 / 导入服务(订单 / 用户导出)
  3. 消息队列消费服务(Kafka 消费)
  4. 高并发 Web 接口(大量请求阻塞在 IO 上)

五、Spring Boot 开启虚拟线程(1 行配置)

你只需要在 application.yml 加一行,无需修改任何业务代码,自动优化所有上述阻塞场景:

spring:
  threads:
    virtual:
      enabled: true # 开启虚拟线程

总结

你日常开发中 90% 的业务代码 都属于 IO 阻塞场景

  • 数据库操作
  • HTTP/Feign 调用
  • Redis/MinIO 操作
  • 文件导出 / 上传
  • Kafka 消息收发

虚拟线程就是为这些阻塞场景量身定制的,能让你的服务吞吐量提升 数倍~十几倍

知识点
多线程