虚拟线程Project Loom:让 Java 高并发变得更简单

36 阅读6分钟

在后端开发中,“高并发”一直是 Java 程序员绕不开的话题。传统 Java 线程虽然使用简单,但在面对大量请求、数据库访问、远程服务调用时,线程资源很容易成为瓶颈。为了解决这个问题,Java 推出了一个重要项目:Project Loom

Project Loom 的核心目标是:让 Java 能够以更低的成本创建和管理大量线程


一、传统 Java 线程有什么问题?

在传统 Java 中,我们创建的 Thread 通常对应操作系统线程。

也就是说:

Java Thread ≈ OS Thread

操作系统线程并不便宜。每创建一个线程,都需要占用一定的内存和调度资源。如果一个系统同时创建成千上万个线程,就可能出现内存占用过高、上下文切换频繁、系统性能下降等问题。

比如一个 Web 服务中,如果每个请求都占用一个传统线程,当请求量非常大时,线程池很容易被打满。

ExecutorService executor = Executors.newFixedThreadPool(200);

这种方式虽然简单,但并发能力受到线程池大小限制。线程池太小,请求要排队;线程池太大,又会带来较高的系统开销。


二、Project Loom 是什么?

Project Loom 是 Java 平台的一个并发改进项目,它最重要的成果就是引入了:

虚拟线程 Virtual Thread

虚拟线程是一种由 JVM 管理的轻量级线程。它不像传统线程那样直接绑定一个操作系统线程,而是由 JVM 负责调度。

可以简单理解为:

传统线程:一个 Java 线程对应一个操作系统线程,成本较高
虚拟线程:大量 Java 虚拟线程复用少量操作系统线程,成本较低

虚拟线程的出现,让 Java 可以轻松创建大量并发任务,而不用像以前那样过度担心线程资源消耗。


三、虚拟线程解决了什么问题?

很多后端系统的瓶颈并不是 CPU,而是 IO 等待。

例如:

访问数据库
调用远程 REST API
读取文件
访问 Redis
上传下载 MinIO 文件
调用大模型接口
调用 OCR 服务

这些操作大部分时间都在“等结果”。传统线程在等待 IO 时,线程会被占住,不能去处理其他任务。

而虚拟线程在等待 IO 时,可以被 JVM 挂起,底层操作系统线程可以去执行其他虚拟线程。

这样一来,系统就可以用更低的成本支撑更多并发请求。


四、虚拟线程怎么使用?

从 Java 21 开始,虚拟线程已经正式可用。最简单的写法如下:

Thread.startVirtualThread(() -> {
    System.out.println("Hello Virtual Thread");
});

如果要批量提交任务,可以使用虚拟线程执行器:

try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
    executor.submit(() -> {
        // 模拟一次远程调用
        callRemoteService();
    });

    executor.submit(() -> {
        // 模拟一次数据库查询
        queryDatabase();
    });
}

每一个任务都会使用一个虚拟线程执行。

这比传统固定线程池更适合大量 IO 型任务。


五、Spring Boot 中如何开启虚拟线程?

如果你使用的是 Java 21 + Spring Boot 3.2 及以上版本,可以在配置文件中开启虚拟线程:

spring:
  threads:
    virtual:
      enabled: true

开启之后,Spring Boot 可以在部分场景下使用虚拟线程处理请求或执行任务。

例如,一个普通的 Controller 仍然可以用同步写法:

@RestController
@RequestMapping("/api")
public class DemoController {

    @GetMapping("/hello")
    public String hello() {
        return callRemoteService();
    }

    private String callRemoteService() {
        // 模拟远程接口调用
        return "Hello Project Loom";
    }
}

使用虚拟线程后,我们不一定要把代码改成复杂的异步回调,也可以继续保持同步代码的可读性。


六、Project Loom 和传统异步编程的区别

在 Project Loom 出现之前,Java 高并发场景常见方案包括:

线程池
CompletableFuture
响应式编程 Reactor / WebFlux
消息队列

这些方案都能解决一部分并发问题,但也会带来额外复杂度。

比如响应式编程虽然性能强,但代码往往会变成链式调用:

return userService.findUser(id)
        .flatMap(user -> orderService.findOrders(user))
        .map(result -> buildResponse(result));

这种方式对初学者来说不太直观,调试也相对复杂。

Project Loom 的优势是:

让开发者用接近传统同步代码的方式,获得更好的并发能力。

也就是说,你可以继续写:

User user = userService.findUser(id);
List<Order> orders = orderService.findOrders(user);
return buildResponse(user, orders);

代码更容易理解,维护成本也更低。


七、Project Loom 适合哪些场景?

Project Loom 特别适合 IO 密集型应用

比如:

Web 后端服务
网关服务
RPC 服务
数据库查询较多的业务系统
微服务之间大量 REST API 调用
文件上传下载服务
Agent / OCR 任务调度系统

以 OCR 系统为例:

Java 后端接收任务
↓
调用 Python OCR 服务
↓
等待 OCR 结果
↓
访问 MinIO 获取文件
↓
写入 PostgreSQL
↓
推送任务状态给前端

这些操作大部分都是 IO 等待。使用虚拟线程后,可以让每个任务用一个虚拟线程执行,代码结构清晰,同时还能提升并发处理能力。


八、Project Loom 不适合哪些场景?

虚拟线程不是万能的。

如果任务是 CPU 密集型,比如:

大量图片压缩
复杂加密计算
视频转码
大规模矩阵计算
本地深度学习推理

这类任务主要消耗 CPU。CPU 核心数量是有限的,虚拟线程不能让 CPU 变多。

所以对于 CPU 密集型任务,仍然需要合理控制并发数量,避免把机器打满。


九、使用 Project Loom 要注意什么?

第一,不要误以为虚拟线程可以无限创建。虽然它很轻量,但任务背后的数据库连接、HTTP 连接、文件句柄等资源仍然是有限的。

第二,虚拟线程适合阻塞式 IO,但要注意第三方库是否兼容。如果某些库内部使用了特殊的阻塞方式,可能无法充分发挥虚拟线程优势。

第三,数据库连接池仍然要控制大小。即使你可以创建十万个虚拟线程,也不代表数据库能承受十万个连接。

第四,CPU 密集任务不要盲目使用虚拟线程,否则可能只是制造更多调度压力。


十、总结

Project Loom 是 Java 并发模型的一次重要升级。它通过虚拟线程降低了线程创建和调度的成本,让 Java 可以更自然地处理大量并发任务。

它最大的价值不是让代码变得更“炫技”,而是让我们可以继续使用简单、直观的同步编程模型,同时获得更强的并发能力。

一句话总结:

Project Loom 让 Java 在高并发 IO 场景下,可以用更简单的代码处理更多任务。

对于现代后端系统来说,尤其是微服务、任务调度、AI Agent、OCR 处理平台这类大量调用外部服务的系统,Project Loom 是非常值得学习和使用的技术。