【Java21】在spring boot中使用虚拟线程

发布于:2025-07-04 ⋅ 阅读:(24) ⋅ 点赞:(0)

前置知识

0.环境说明

用于验证的版本:

spring boot 3.1中就支持使用jdk21的虚拟线程了,但是在spring boot 3.2中可以直接通过配置的方式开启虚拟线程。不过本文编写的时候spring boot 3.3.3已经GA了,他在3.2的基础上又对虚拟线程进行了进一步地适配,所以本文章中的版本为3.3

在spring boot 3.2.9中配置spring.threads.virtual.enabled=true即可启用虚拟线程

1.原理解析

开启了这个配置后,对我们的spring boot服务有什么影响呢?

我们在spring boot的源码中搜索spring.threads.virtual.enabled即可看到这个配置在spring boot中生效在了什么地方。
首先我们可以看到如下的代码(通过代码注释也能看出,这个配置就是在3.2.0引入的)

/**
 * Threading of the application.
 *
 * @author Moritz Halbritter
 * @since 3.2.0
 */
public enum Threading {

	/**
	 * Platform threads. Active if virtual threads are not active.
	 */
	PLATFORM {

		@Override
		public boolean isActive(Environment environment) {
			return !VIRTUAL.isActive(environment);
		}

	},
	/**
	 * Virtual threads. Active if {@code spring.threads.virtual.enabled} is {@code true}
	 * and running on Java 21 or later.
	 */
	VIRTUAL {

		@Override
		public boolean isActive(Environment environment) {
			return environment.getProperty("spring.threads.virtual.enabled", boolean.class, false)
					&& JavaVersion.getJavaVersion().isEqualOrNewerThan(JavaVersion.TWENTY_ONE);
		}

	};

	/**
	 * Determines whether the threading is active.
	 * @param environment the environment
	 * @return whether the threading is active
	 */
	public abstract boolean isActive(Environment environment);

}

接下来我们看一下VIRTUAL.isActive(environment)这个方法都用在了哪里(哪里用到了,就说明哪里的虚拟线程是通过这个配置生效的)

可以看到他被用在如下的地方:

  • spring-webflux(区别于spring web的一个响应式web开发框架)的阻塞配置中(如果阻塞了使用什么类型的线程去进行阻塞)
  • 注解ConditionalOnThreading的判断条件OnThreadingCondition
    • Rabbit MQ——一个消息队列,启用虚拟线程则执行configurer.setTaskExecutor(new VirtualThreadTaskExecutor("rabbit-direct-"));configurer.setTaskExecutor(new VirtualThreadTaskExecutor("rabbit-simple-"));
    • JedisConnection——一个redis客户端,启用虚拟线程则执行
      	SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor("redis-");
          executor.setVirtualThreads(true);
          factory.setExecutor(executor);
      
    • LettuceConnection——一个redis客户端,启用虚拟线程则执行和Jedis类似的代码,都是把Executor设置为一个启用了虚拟线程的new SimpleAsyncTaskExecutor("redis-");
    • Kafka——一个消息队列,启用虚拟线程则执行和Jedis类似的两码,把Executor设置为一个启用了虚拟线程的new SimpleAsyncTaskExecutor("kafka-");
    • TaskExecutorConfigurations——spring boot中的任务调度器配置类,@Async(异步任务注解)所注解的方法会根据配置决定是用平台线程执行还是虚拟线程执行
    • TaskSchedulingConfigurations——spring boot中定时任务配置类,@Scheduled(定时任务注解)所注解的方法会根据配置决定是用平台线程执行还是虚拟线程执行
    • EmbeddedWebServerFactoryCustomizerAutoConfiguration——通常我们最关心的地方,这里定义了spring web的默认容器tomcat的线程配置(Jetty的工作线程配置也在一起),如果开启了虚拟线程,则tomcat会使用虚拟线程作为执行器(再也不用考虑tomcat默认200线程的问题了!!!)
  • Pulsar:一个分布式消息流平台,启用虚拟线程则执行containerProperties.setConsumerTaskExecutor(new VirtualThreadTaskExecutor("pulsar-consumer-"));readerContainerProperties.setReaderTaskExecutor(new VirtualThreadTaskExecutor("pulsar-reader-"));

2.spring boot的方案

通过在spring boot的源码搜索@ConditionalOnThreading(Threading.VIRTUAL),可以看到spring boot会根据虚拟线程的开启与否来选择注入不同的bean,我们以spring-data-redis为例,其具体代码如下:

    @Bean
	@ConditionalOnMissingBean(RedisConnectionFactory.class)
	@ConditionalOnThreading(Threading.PLATFORM)
	LettuceConnectionFactory redisConnectionFactory(
			ObjectProvider<LettuceClientConfigurationBuilderCustomizer> builderCustomizers,
			ClientResources clientResources) {
		return createConnectionFactory(builderCustomizers, clientResources);
	}

	@Bean
	@ConditionalOnMissingBean(RedisConnectionFactory.class)
	@ConditionalOnThreading(Threading.VIRTUAL)
	LettuceConnectionFactory redisConnectionFactoryVirtualThreads(
			ObjectProvider<LettuceClientConfigurationBuilderCustomizer> builderCustomizers,
			ClientResources clientResources) {
		LettuceConnectionFactory factory = createConnectionFactory(builderCustomizers, clientResources);
		SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor("redis-");
		executor.setVirtualThreads(true);
		factory.setExecutor(executor);
		return factory;
	}

通过代码不难看出,spring boot 3.2通过@ConditionalOnThreading注解的方式,实现了虚拟线程和平台线程的动态配置。

如果我们自己需要开发一个能够同时支持平台线程和虚拟线程的sdk,可以复用这个注解。

3.注意事项(施工中,欢迎补充)

  • 使用虚拟线程本身要注意的5个点:
    1. 大方使用“一个请求一个线程”的开发方式
    2. 不需要对虚拟线程进行池化
    3. 使用信号量控制并发
    4. 慎用ThreadLocal,使用ScopeValue(java21中还是预览状态)代替
    5. 使用ReturnLock替代synchronized(这个问题似乎在jdk23中会永久解决)
  • java21默认的垃圾回收器G1和其自身的JIT编译器C2似乎有冲突,会导致jvm crash?(这里不能确定,似乎在高版本jdk 21.0.7中进行了修复,但是我比较了openjdk的源码,并没能找到具体的改动指向这个问题)