🌺The Begin🌺点点关注,收藏不迷路🌺


📌 本文导读:本文将从协程的核心概念出发,详细解析Java中协程的实现方式——虚拟线程(Virtual Threads),涵盖原理、使用方法和最佳实践。全文包含六大章节3个彩色流程图10个代码示例。预计阅读时间28分钟


一、🔴 什么是协程?——轻量级并发的革命

1.1 🟠 协程的核心定义

协程(Coroutine),又称微线程纤程(Fiber),是一种用户态的轻量级线程,由程序自身控制调度,而非由操作系统内核调度。

与传统的线程不同,协程可以在执行过程中主动挂起(暂停),让出CPU控制权,在适当的时候再恢复执行。这种"协作式"的多任务处理方式,与线程的"抢占式"调度形成鲜明对比。

// 🟡 协程的典型执行流程(概念示例)
// 协程A执行 -> 遇到await/yield主动挂起 -> 协程B执行 -> 完成后恢复协程A

1.2 🟢 协程 vs 线程 vs 进程:核心区别

维度 进程 线程 协程
调度者 操作系统内核 操作系统内核 用户程序(程序员)
切换开销 极高(页表切换) 中等(内核态切换) 极低(用户态切换)
内存占用 GB级别 MB级别 KB级别
并发数量 数百 数千 数百万
通信方式 IPC(管道、消息队列等) 共享内存 + 锁 无需锁(单线程内串行)

1.3 🟣 协程的两大核心优势

① 极高的执行效率:协程切换不是线程切换,不涉及内核态和用户态的转换,完全在用户空间完成,因此没有线程切换的开销。线程数量越多,协程的性能优势就越明显。

② 无需锁机制:由于多个协程运行在同一个线程内,同一时刻只有一个协程在执行,不存在同时写共享变量的冲突,因此不需要加锁,执行效率比多线程高很多。

⚠️ 注意:协程的"无需锁"是指同一线程内的协程间无需加锁。如果涉及多个线程间的协程,仍需考虑同步问题。


二、🔵 流程图:进程、线程、协程的层级关系

🟣 线程2

🔵 线程1

🟢 进程1

🟠 操作系统

进程1

进程2

进程...

线程1

线程2

线程...

协程A

协程B

协程C

协程...

协程X

协程Y

📌 关键理解:一个系统有多个进程 → 一个进程有多个线程 → 一个线程有多个协程。


三、🟡 Java支持协程吗?——虚拟线程时代已来临

3.1 🔴 历史背景:Java为何迟迟没有协程?

在Java 21之前,Java官方并未提供协程支持。主要原因包括:

  1. Java的线程模型:Java线程一直以来是操作系统线程的"薄封装"(thin wrapper),直接映射到内核线程
  2. 生态替代方案:通过线程池NIONetty响应式编程等框架,已经可以解决高并发问题
  3. 语言保守性:Java作为企业级语言,对重大特性引入较为谨慎

3.2 🟢 Java 21的突破:虚拟线程(Virtual Threads)

Java终于在JDK 19中引入了虚拟线程(预览版),并在JDK 21中正式发布!

虚拟线程(Virtual Threads)是Java对协程的实现,由JVM管理,不直接映射到操作系统线程,可以在单个操作系统线程上调度数百万个虚拟线程。

虚拟线程与平台线程(传统Java线程)的对比

特性 平台线程(Platform Thread) 虚拟线程(Virtual Thread)
映射关系 1:1 映射到OS线程 M:N 多个映射到少量OS线程
创建开销 高(MB级栈内存) 极低(KB级,堆上分配)
切换开销 内核调度,昂贵 用户态调度,低廉
并发数量 数千 数百万
线程类型 默认用户线程 守护线程(不可修改)

3.3 🔵 虚拟线程的底层原理

虚拟线程由JVM调度,使用**延续(Continuation)**机制实现:

  1. 挂载(Mount):虚拟线程被分配到一个载体线程(Carrier Thread,即OS线程)上执行
  2. 阻塞操作:当虚拟线程遇到阻塞(如I/O等待),JVM会**卸载(Unmount)**该虚拟线程,释放载体线程
  3. 恢复:I/O完成后,虚拟线程被重新挂载到某个空闲的载体线程上继续执行

这种方式使得阻塞操作不再阻塞底层OS线程,大幅提升了线程利用率。


四、🟠 流程图:虚拟线程的工作机制

🔵 载体线程池
(OS线程)

🟢 JVM层

挂载

挂载

阻塞I/O

释放

重新挂载

I/O完成

挂载到空闲载体

虚拟线程A
运行中

虚拟线程B
阻塞等待I/O

虚拟线程C
就绪状态

虚拟线程D
运行中

载体线程1

载体线程2

载体线程...

🟠 卸载

🟣 恢复


五、🟣 Java虚拟线程实战指南

5.1 🟤 创建虚拟线程的四种方式(JDK 21+)

方式一:Thread.startVirtualThread() —— 最常用
// 🟢 创建并直接启动虚拟线程
Thread.startVirtualThread(() -> {
    System.out.println("Hello from Virtual Thread: " + Thread.currentThread());
});
方式二:Thread.ofVirtual().unstarted() —— 延迟启动
// 🟡 先创建,后手动启动
Thread vt = Thread.ofVirtual().unstarted(() -> {
    System.out.println("Virtual thread running");
});
vt.start();  // 显式启动
方式三:虚拟线程工厂
// 🟠 通过工厂批量创建
ThreadFactory factory = Thread.ofVirtual().factory();
Thread vt = factory.newThread(() -> {
    System.out.println("Created by factory");
});
vt.start();
方式四:虚拟线程池(ExecutorService)
// 🔵 使用支持虚拟线程的线程池
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
    executor.submit(() -> {
        System.out.println("Task in virtual thread pool");
    });
    // 自动关闭,等待所有任务完成
}

5.2 🔴 命名虚拟线程(便于调试)

// 🟣 给虚拟线程命名
Thread vt = Thread.ofVirtual()
    .name("my-virtual-thread-", 1)  // 自动递增序号
    .start(() -> {
        System.out.println(Thread.currentThread().getName());
    });

5.3 🟠 结构化并发(Structured Concurrency)

Java 21还引入了结构化并发API,用于管理多个虚拟线程的生命周期:

// 🔴 使用 StructuredTaskScope 管理多个并发任务
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
    // 🟢 同时启动两个虚拟线程
    var userFuture = scope.fork(() -> fetchUser(id));
    var ordersFuture = scope.fork(() -> fetchOrders(id));
    
    scope.join();              // 等待所有任务完成
    scope.throwIfFailed();     // 如有异常则抛出
    
    // 🟡 获取结果
    var user = userFuture.resultNow();
    var orders = ordersFuture.resultNow();
    return aggregate(user, orders);
}

六、🟤 流程图:虚拟线程选型决策树

I/O密集型
网络请求/数据库访问

CPU密集型
计算任务

混合型

📋 需要高并发处理

任务类型是什么?

🟢 虚拟线程
最佳选择

🟠 平台线程
数量=CPU核心数

🟡 虚拟线程+
平台线程池隔离

🟣 优势:
1. 可创建百万级线程
2. 阻塞不浪费OS资源
3. 代码简单同步风格

🔴 注意:
虚拟线程不会加速CPU计算

🔵 建议:
I/O部分用虚拟线程
计算部分保留平台线程


七、🔵 避坑指南与最佳实践

7.1 🔴 常见陷阱

陷阱一:在同步块中进行阻塞I/O(Pinning)
// ❌ 错误:持有synchronized锁时进行I/O操作
synchronized (lock) {
    // 虚拟线程会被"钉住",载体线程无法释放
    httpClient.send(request);  // 阻塞I/O
}

// ✅ 正确:使用ReentrantLock替代
ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
    httpClient.send(request);
} finally {
    lock.unlock();
}

📌 被"钉住"(Pinned)的虚拟线程会阻塞载体线程,降低并发性能。

陷阱二:ThreadLocal滥用
// ❌ 不推荐:百万级虚拟线程都创建ThreadLocal
ThreadLocal<String> context = new ThreadLocal<>();
// 每个虚拟线程都有独立副本,内存消耗巨大

// ✅ 推荐:使用Scoped Values(Java 21预览特性)
ScopedValue<String> context = ScopedValue.newInstance();
陷阱三:对CPU密集型任务使用虚拟线程
// ❌ 错误:CPU密集型任务不会因虚拟线程而变快
Thread.startVirtualThread(() -> {
    // 大量计算,始终占用载体线程
    heavyComputation();
});
// ✅ 正确:使用平台线程池,数量=CPU核心数

7.2 🟢 最佳实践清单

序号 实践建议 说明
优先用于I/O密集型任务 Web服务、数据库访问、HTTP调用
避免synchronized + 阻塞I/O 使用ReentrantLock或缩短锁持有时间
使用try-with-resources管理线程池 确保正确关闭
命名虚拟线程 便于调试和监控
限制外部资源并发数 数据库连接池、HTTP连接池仍需控制
监控Pinning事件 添加-Djdk.tracePinnedThreads=full

八、🟣 总结:Java协程的过去、现在与未来

阶段 Java版本 状态 说明
过去 JDK 1.0 - 20 ❌ 无原生协程 依赖线程池、NIO、Netty等变通方案
现在 JDK 21+ ✅ 虚拟线程正式版 原生协程支持,推荐用于I/O密集型
未来 JDK 后续版本 🚀 持续优化 结构化并发、Scoped Values等配套完善

核心结论

  • 协程是用户态轻量级线程,由程序自主调度,切换开销极低
  • Java通过虚拟线程实现协程,JDK 21正式发布,可创建数百万并发线程
  • 适用于I/O密集型场景,对CPU密集型任务无性能提升
  • 推荐使用 Executors.newVirtualThreadPerTaskExecutor()Thread.startVirtualThread()


📝 本文基于 JDK 21 LTS 编写,所有代码示例已验证。
如果您觉得有帮助,欢迎 点赞 + 收藏 + 转发
有任何疑问,请在评论区留言交流。


在这里插入图片描述


🌺The End🌺点点关注,收藏不迷路🌺

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐