文章目錄
- 0.環境說明
- 1.原理解析
- 2.spring boot的方案
- 3.注意事項(施工中,歡迎補充)
前置知識
- 虛擬線程VT(Virtual Thread)
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 {@Overridepublic 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 {@Overridepublic 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的源碼,并沒能找到具體的改動指向這個問題)