前置知识
0.环境说明
用于验证的版本:
- spring boot: 3.3.3
- jdk: OpenJDK 21.0.5
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线程的问题了!!!)
- Rabbit MQ——一个消息队列,启用虚拟线程则执行
- 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个点:
- 大方使用“一个请求一个线程”的开发方式
- 不需要对虚拟线程进行池化
- 使用信号量控制并发
- 慎用ThreadLocal,使用ScopeValue(java21中还是预览状态)代替
- 使用ReturnLock替代synchronized(这个问题似乎在jdk23中会永久解决)
- java21默认的垃圾回收器G1和其自身的JIT编译器C2似乎有冲突,会导致jvm crash?(这里不能确定,似乎在高版本jdk 21.0.7中进行了修复,但是我比较了openjdk的源码,并没能找到具体的改动指向这个问题)