虚拟线程(Virtual Threads)是Java 21中最引人注目的特性,也是Project Loom的核心成果。虚拟线程解决了传统Java并发模型的根本矛盾:线程是昂贵的操作系统资源,但高并发应用需要大量线程来处理阻塞I/O操作。虚拟线程将线程的创建成本降低了数千倍,使百万并发成为现实。
参考:https://vhjpe.cn/category/chanpin-pingce.html
传统线程模型的困境:Java的Thread从1.0开始就映射到操作系统线程(OS线程)。OS线程的创建成本高(约1MB栈内存),上下文切换开销大,数量受限(通常几千到几万)。这意味着传统的“一个请求一个线程”模型无法支撑C10K问题。为了解决这个问题,开发者被迫使用异步编程(NIO、Netty、CompletableFuture),牺牲了代码的可读性和可维护性。
虚拟线程是JVM管理的轻量级线程,不直接映射到OS线程。多个虚拟线程可以运行在同一个OS线程(称为载体线程)上。当虚拟线程执行阻塞操作(如socket.read())时,它会从载体线程上卸载(yield),载体线程切换到另一个可运行的虚拟线程。当I/O完成后,虚拟线程被重新挂载到某个载体线程上继续执行。
虚拟线程的调度由JVM的FIFO任务队列实现。调度器使用ForkJoinPool(固定大小的work-stealing池)作为载体线程池。虚拟线程的栈帧存储在堆上(而不是OS线程的栈中),因此可以轻松支持数百万个虚拟线程,每个虚拟线程的初始栈大小可配置(通常几百字节)。
虚拟线程的API与普通线程几乎相同。创建虚拟线程:Thread.startVirtualThread(runnable)或Thread.ofVirtual().start(runnable)。Executors.newVirtualThreadPerTaskExecutor()返回一个为每个任务创建新虚拟线程的执行器。虚拟线程也支持ThreadLocal、InheritableThreadLocal,但使用ThreadLocal时需要谨慎——因为虚拟线程数量巨大,ThreadLocal可能消耗大量内存。
参考:https://vhjpe.cn/category/meirong-zhishi.html
虚拟线程与平台线程的对比:虚拟线程适合执行大量阻塞I/O操作的任务,不适合CPU密集型任务(如果虚拟线程一直运行而不阻塞,调度器无法切换,一个载体线程只能运行一个虚拟线程)。虚拟线程的创建和切换成本比平台线程低几个数量级,但比异步回调稍高。
虚拟线程与现有框架的集成:Tomcat、Jetty、Netty等框架正在适配虚拟线程。Tomcat已经支持虚拟线程作为请求处理线程。Spring Framework 6和Spring Boot 3提供了对虚拟线程的一等支持——@Async方法可以在虚拟线程上执行。JDBC驱动需要适配虚拟线程,因为传统的JDBC驱动会阻塞载体线程,抵消虚拟线程的优势。
虚拟线程的注意事项:同步块和同步方法可能导致载体线程被固定(pinned),即使虚拟线程阻塞,载体线程也无法切换。JVM会检测这种情况并增加载体线程数量作为缓解,但性能会下降。解决方案是使用ReentrantLock替代synchronized。本地方法和外部函数(FFI)也会导致固定。ThreadLocal的使用需要重新评估,因为虚拟线程数量大,ThreadLocal的内存占用会放大。线程池的概念需要调整——传统的线程池限制了并发数,但虚拟线程池的核心目标是创建新线程,而不是复用线程。
虚拟线程的性能特征:创建100万个虚拟线程只需几秒(而平台线程需要几分钟或直接失败)。虚拟线程的阻塞切换开销约为几微秒,远低于平台线程的上下文切换(微秒到毫秒级)。虚拟线程的内存占用约为几百字节(平台线程约1MB)。虚拟线程的启动时间约为1-2微秒(平台线程约1毫秒)。
虚拟线程对编程模型的影响:虚拟线程最重要的贡献是让阻塞重新变得可接受。开发者可以回到简单直接的“一个请求一个线程”模型,在需要I/O时直接阻塞,而不必编写复杂的异步代码。这大大降低了并发编程的门槛,也使代码更容易调试和维护。
参考:https://vhjpe.cn/category/hufu-jiqiao.html
结构化并发是虚拟线程的自然延伸。结构化并发将并发任务组织为树状结构,确保子任务的生命周期不会超出父任务的范围。StructuredTaskScope(预览功能,Java 21)提供了结构化并发的API。结构化并发的好处包括:自动取消(如果父任务失败,所有子任务被取消)、错误隔离、以及资源泄漏防止。
作用域值(Scoped Values,Java 20预览,Java 21孵化)是ThreadLocal的替代方案,专为虚拟线程设计。作用域值是不可变的,只能在有限的作用域内设置,在线程间传递。与ThreadLocal不同,作用域值不会在虚拟线程数量巨大时导致内存泄漏,且传递效率更高。
虚拟线程的限制:虚拟线程不能与ThreadGroup一起使用(已过时)。虚拟线程不支持stop、suspend、resume等已废弃的方法。虚拟线程的优先级被忽略。虚拟线程的调试工具(如JDB、JMC)正在逐步适配。
虚拟线程的启用:Java 21中虚拟线程是正式功能,无需预览标志。迁移现有代码通常只需将Executors.newCachedThreadPool()替换为Executors.newVirtualThreadPerTaskExecutor()。但需要注意依赖库的兼容性——某些库可能在虚拟线程中表现异常。
虚拟线程的未来:虚拟线程是Java并发模型的最大变革,它将改变服务端Java的编程范式。随着生态系统对虚拟线程的适配,我们可以期待:Web框架默认使用虚拟线程处理请求;数据库驱动支持真正的异步I/O(不阻塞载体线程);以及更简单的并发编程模型,无需理解Reactor、RxJava或CompletableFuture。
虚拟线程不是银弹。它最适合I/O密集型应用,如Web服务、微服务、API网关。对于CPU密集型应用(如视频编码、机器学习),传统平台线程仍然是最佳选择。但大多数Java应用都是I/O密集型的——它们花大量时间等待数据库、缓存、消息队列和外部服务。对于这些应用,虚拟线程将带来显著的简化,而不仅仅是性能提升。
参考:https://vhjpe.cn/