跳转至

Java有协程吗? (1)

好,这个问题问得挺到点子上的,我从实际开发感受和底层原理两个维度帮你拆解一下。

一、先给结论

Java 语言层面没有传统意义上的“协程”关键字,但是从 JDK 19 开始,已经通过虚拟线程(Virtual Threads)提供了协程的核心能力。

说人话就是:Java 现在有协程了,只是它不叫 coroutine,而叫虚拟线程,它属于 有栈协程。


二、为什么这个话题会存在——先搞清“协程”到底是什么

协程本质上就两点:

  1. 用户态调度:切换不由操作系统内核管理,而是由运行时/库在用户空间完成。

  2. 挂起与恢复:可以在某个点暂停(比如等 I/O),之后从暂停处继续执行,不阻塞底层系统线程。

带着这两个标准去套 Java 的发展史,就会很清晰:

  • 传统 Java 线程(Thread):1:1 映射到 OS 内核线程,创建成本高、切换靠内核、内存占用大。这根本不是协程。

  • 线程池 + 异步回调(CompletableFuture 等):能解决部分问题,但代码心智负担重,挂起后恢复靠回调地狱,也不是协程。

  • Kotlin 协程(在 JVM 上):通过编译器生成状态机,实现了无栈协程,可以用同步方式写异步代码。但这不是 Java 语言自己的东西。

  • Project Loom 的虚拟线程:这是 Java 自己搞出来的,有栈协程,挂起时会保存整个调用栈到堆里,切走载体线程,恢复时再挂载回来。它完全满足了协程的定义。

所以,如果面试官问“Java 有协程吗?”,想听到的就是你对这一演变过程的理解,而不是简单答个“没有”或“有”。


三、虚拟线程到底是怎么玩的(结合时序图)

虚拟线程的调度模型可以用一个很简单的例子说明:你创建了一堆虚拟线程,它们都通过一个调度器挂载到少数几个平台线程(也就是原来的内核线程)上执行。

当虚拟线程 A 遇到阻塞操作(比如 Socket read / sleep)时,调度过程是这样的:

deepseek_mermaid_20260613_6dad07.png

从这个图可以看出来:

  • 阻塞不会浪费平台线程:虚拟线程 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 实现,再配上时序图把调度过程可视化。这样的回答既不像背稿,又有逻辑纵深,还能体现实战经验。希望能帮到你面试时用上。