Java有协程吗? (1)
好,这个问题问得挺到点子上的,我从实际开发感受和底层原理两个维度帮你拆解一下。
一、先给结论¶
Java 语言层面没有传统意义上的“协程”关键字,但是从 JDK 19 开始,已经通过虚拟线程(Virtual Threads)提供了协程的核心能力。
说人话就是:Java 现在有协程了,只是它不叫 coroutine,而叫虚拟线程,它属于 有栈协程。
二、为什么这个话题会存在——先搞清“协程”到底是什么¶
协程本质上就两点:
-
用户态调度:切换不由操作系统内核管理,而是由运行时/库在用户空间完成。
-
挂起与恢复:可以在某个点暂停(比如等 I/O),之后从暂停处继续执行,不阻塞底层系统线程。
带着这两个标准去套 Java 的发展史,就会很清晰:
-
传统 Java 线程(Thread):1:1 映射到 OS 内核线程,创建成本高、切换靠内核、内存占用大。这根本不是协程。
-
线程池 + 异步回调(CompletableFuture 等):能解决部分问题,但代码心智负担重,挂起后恢复靠回调地狱,也不是协程。
-
Kotlin 协程(在 JVM 上):通过编译器生成状态机,实现了无栈协程,可以用同步方式写异步代码。但这不是 Java 语言自己的东西。
-
Project Loom 的虚拟线程:这是 Java 自己搞出来的,有栈协程,挂起时会保存整个调用栈到堆里,切走载体线程,恢复时再挂载回来。它完全满足了协程的定义。
所以,如果面试官问“Java 有协程吗?”,想听到的就是你对这一演变过程的理解,而不是简单答个“没有”或“有”。
三、虚拟线程到底是怎么玩的(结合时序图)¶
虚拟线程的调度模型可以用一个很简单的例子说明:你创建了一堆虚拟线程,它们都通过一个调度器挂载到少数几个平台线程(也就是原来的内核线程)上执行。
当虚拟线程 A 遇到阻塞操作(比如 Socket read / sleep)时,调度过程是这样的:

从这个图可以看出来:
-
阻塞不会浪费平台线程:虚拟线程 A 睡大觉的时候,平台线程 1 扭头就去执行虚拟线程 B 了。
-
挂载/卸载过程对程序员透明:你写的代码还是
Thread.sleep()或socket.read(),没有任何新 API,协程特性由 JVM 在底层搞定。 -
恢复可能在不同载体线程上:因为堆中保存的栈是完整的,切到哪个平台线程跑都一样。
这就是“有栈协程”的典型工作方式,和 Go 的 goroutine 很类似,但 goroutine 是无栈且由 Go 运行时管理分段栈,Java 的虚拟线程则用堆保存栈帧。
四、再往深挖一点:为什么不直接用“协程”这个名字?¶
因为 Java 的设计哲学是向下兼容。
-
“协程”通常暗示一套新的语言结构,比如
suspend关键字、特殊的返回类型等,这会割裂现有的代码生态。 -
虚拟线程的目标就是:让你手上那堆老旧的、用 Thread 和阻塞 I/O 写的服务器代码,几乎不用改就能获得协程式的超高吞吐。 只要把
new Thread(...)换成Thread.ofVirtual().start(...),你的应用就能轻松支撑上百万并发,而不需要重写成反应式流或 Kotlin 协程。
所以,它不是发明新玩具,而是把已有的阻塞代码直接变成“协程化”的执行。这也是为什么它叫虚拟线程,而不叫 JCouroutine。
五、如果面试时想给出高水准总结,可以这样说¶
“Java 传统上没有协程,但 Project Loom 引入的虚拟线程本质上就是有栈协程。 它通过 JVM 层面的调度,让阻塞操作自动触发栈的卸载和挂载,释放底层平台线程,从而用极少的内核线程支撑海量并发。 开发体验上,可以用同步阻塞风格写异步高并发的代码,同时避免了反应式编程的回调复杂度。 目前(JDK 21+)已经是正式特性,生产可用。”
这比干巴巴地说“有”或“没有”,要扎实得多,也更能体现你对技术演进的思考。
六、顺手对比一下,防止跟 Kotlin 协程搞混¶
知道这个区别,当你面试被问到“那你觉得虚拟线程和 Kotlin 协程是什么关系”的时候,就能接上话了。
最后,整个答案没有用什么生僻术语堆砌,而是从你写代码的感受一路推到 JVM 实现,再配上时序图把调度过程可视化。这样的回答既不像背稿,又有逻辑纵深,还能体现实战经验。希望能帮到你面试时用上。