Spring Boot中启用虚拟线程的四个场景和源码

Spring Boot 3和Spring Framework 6正式支持Virtual Thread。本文总结了如何使用虚拟线程替换基于 Spring Boot 的项目中处理Spring Web MVC请求、@Async和协程执行的平台线程。(以下内容均已在生产环境中验证。)

虚拟线程特性

  • Java 19/20开始作为预览功能提供,并在Java 21(即LTSVirtual Thread版本)中作为完整功能提供。
  • 传统的Platform Thread直接包装操作系统线程,无法用于IO或计算造成的阻塞持续时间,例如网络和数据库操作。另一方面,虚拟线程作为JVM管理的逻辑单元要轻得多,与物理操作系统线程隔离。至关重要的是,当发生阻塞时,正在使用的操作系统线程可以被另一个虚拟线程利用,从而显着提高并发处理。与反应式方法不同,这使得开发人员无需进行重大更改即可在传统顺序编程中实现显着的性能改进。(这代表了JVM社区多年来的重大创新。)
  • Java社区做出了细致的努力来保持与Virtual Thread的引入的向后兼容性。Executors.newVirtualThreadPerTaskExecutor()可以用来轻松创建Executor对象。将其与 servlet 容器和 Kotlin 协程集成可以立即使用虚拟线程,而无需更改现有代码。
  • 有两种使用虚拟线程的方法。Java 19/20--release 19 --enable-preview要求您在项目构建时和--enable-preview运行时添加该选项。另一方面,Java 21不需要添加此选项。

1、将 HTTP 请求处理切换到虚拟线程

  • 在Java生态系统中,servlet 容器通过物理平台线程处理请求,这些线程是为每个单元请求包装的操作系统线程。以下代码允许您指示Apache Tomcat ( Spring Boot中嵌入的 servlet 实现)使用虚拟线程而不是平台线程来处理所有请求

import org.apache.coyote.ProtocolHandler
import org.springframework.boot.web.embedded.tomcat.TomcatProtocolHandlerCustomizer
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import java.util.concurrent.Executors

@Configuration
class TomcatConfig {

    @Bean
    fun protocolHandlerVirtualThreadExecutorCustomizer(): TomcatProtocolHandlerCustomizer<*>? {
        return TomcatProtocolHandlerCustomizer<ProtocolHandler> { protocolHandler: ProtocolHandler ->
            protocolHandler.executor = Executors.newVirtualThreadPerTaskExecutor()
        }
    }
}

2、将异步执行切换到虚拟线程

  • Spring Boot生态系统中执行异步逻辑最方便的方法之一@Async,传统上运行在基于平台线程池的AsyncTaskExecutor实现分配的线程上。这可以切换到虚拟线程,如下所示。

import org.slf4j.MDC
import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.core.task.AsyncTaskExecutor
import org.springframework.core.task.TaskDecorator
import org.springframework.core.task.support.TaskExecutorAdapter
import org.springframework.scheduling.annotation.EnableAsync
import java.util.concurrent.Executors

@Configuration
@EnableAsync
class AsyncConfig {

    @Bean(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME)
    fun asyncTaskExecutor(): AsyncTaskExecutor {

        val taskExecutor = TaskExecutorAdapter(Executors.newVirtualThreadPerTaskExecutor())
        taskExecutor.setTaskDecorator(LoggingTaskDecorator())

        return taskExecutor
    }
}

class LoggingTaskDecorator : TaskDecorator {

    override fun decorate(task: Runnable): Runnable {

        val callerThreadContext = MDC.getCopyOfContextMap()

        return Runnable {
            callerThreadContext?.let {
                MDC.setContextMap(it)
            }
            task.run()
        }
    }
}


3、将调度程序执行切换到虚拟线程

  • 在Spring Boot生态系统中,@Scheduled根据定义的规则在特定时间执行的任务传统上运行在基于平台线程池的ThreadPoolTask​​Executor实现分配的线程上。这可以切换到虚拟线程,如下所示。

import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.scheduling.TaskScheduler
import org.springframework.scheduling.annotation.EnableScheduling
import org.springframework.scheduling.concurrent.ConcurrentTaskScheduler
import java.util.concurrent.Executors

@Configuration
@EnableScheduling
class SchedulingConfig {

    @Bean
    fun taskScheduler(): TaskScheduler {

        return ConcurrentTaskScheduler(
            Executors.newScheduledThreadPool(0, Thread.ofVirtual().factory())
        )
    }
}


4、将 Kotlin 协程执行切换到虚拟线程

  • Kotlin很早就通过协程为开发者提供了类似的挂起功能,甚至是在Virtual Thread出现之前。通过编写下面的代码并使用Dispatchers.LOOM(它应用虚拟线程而不是传统使用的Dispatchers.IO ),可以在虚拟线程上执行协程。

import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.asCoroutineDispatcher
import java.util.concurrent.Executors

val Dispatchers.LOOM: CoroutineDispatcher
    get() = Executors.newVirtualThreadPerTaskExecutor().asCoroutineDispatcher()