虛擬線程在Spring?Boot中的正確使用方式及最佳實(shí)踐
一、概述
Java 21引入了虛擬線程(Virtual Threads),這是Project Loom的核心特性。虛擬線程是輕量級(jí)線程,可以顯著提高應(yīng)用程序的并發(fā)處理能力,特別適合I/O密集型任務(wù)。
在Spring Boot 3.2+中,已經(jīng)內(nèi)置了對(duì)虛擬線程的支持,可以通過簡單的配置啟用。
二、Spring Boot 3.2+ 虛擬線程自動(dòng)配置
1. 啟用虛擬線程(已在項(xiàng)目中配置)
在 application.yml 中已經(jīng)配置:
spring:
threads:
virtual:
enabled: true
這個(gè)配置會(huì)自動(dòng):
- 為
@Async方法啟用虛擬線程執(zhí)行器 - 為 Spring MVC 的請(qǐng)求處理啟用虛擬線程
- 為 Spring WebFlux 啟用虛擬線程
2. 驗(yàn)證虛擬線程是否啟用
可以通過以下方式驗(yàn)證:
@SpringBootTest
public class VirtualThreadTest {
@Test
public void testVirtualThread() {
Thread thread = Thread.ofVirtual().start(() -> {
System.out.println("虛擬線程名稱: " + Thread.currentThread().getName());
System.out.println("是否為虛擬線程: " + Thread.currentThread().isVirtual());
});
try {
thread.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
三、使用方式
方式一:Spring Boot自動(dòng)配置(推薦)
1.1 異步方法中使用虛擬線程
配置類改造(推薦):
/**
* 異步線程池配置(虛擬線程版本)
* 當(dāng) spring.threads.virtual.enabled=true 時(shí)使用虛擬線程
*
* @author
* @date 2024-10-31
*/
@Slf4j
@Configuration
@EnableAsync
public class VirtualThreadAsyncConfig implements AsyncConfigurer {
/**
* 虛擬線程異步執(zhí)行器
* Spring Boot 3.2+ 會(huì)自動(dòng)創(chuàng)建虛擬線程執(zhí)行器,這里提供手動(dòng)配置示例
*/
@Bean(name = "virtualThreadExecutor")
@ConditionalOnProperty(name = "spring.threads.virtual.enabled", havingValue = "true", matchIfMissing = false)
public Executor virtualThreadExecutor() {
return new TaskExecutorAdapter(Executors.newVirtualThreadPerTaskExecutor());
}
/**
* 默認(rèn)異步執(zhí)行器
* 如果啟用虛擬線程,Spring Boot會(huì)自動(dòng)使用虛擬線程執(zhí)行器
*/
@Override
public Executor getAsyncExecutor() {
// Spring Boot 3.2+ 會(huì)自動(dòng)使用虛擬線程執(zhí)行器(如果啟用)
// 如果需要手動(dòng)指定,可以返回 virtualThreadExecutor()
return Executors.newVirtualThreadPerTaskExecutor();
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new SimpleAsyncUncaughtExceptionHandler();
}
}
使用示例:
@Service
@Slf4j
public class ExampleService {
/**
* 使用默認(rèn)虛擬線程執(zhí)行器
*/
@Async
public CompletableFuture<String> asyncMethod1() {
log.info("當(dāng)前線程: {}, 是否為虛擬線程: {}",
Thread.currentThread().getName(),
Thread.currentThread().isVirtual());
// 執(zhí)行異步任務(wù)
return CompletableFuture.completedFuture("完成");
}
/**
* 指定使用虛擬線程執(zhí)行器
*/
@Async("virtualThreadExecutor")
public CompletableFuture<String> asyncMethod2() {
log.info("當(dāng)前線程: {}, 是否為虛擬線程: {}",
Thread.currentThread().getName(),
Thread.currentThread().isVirtual());
return CompletableFuture.completedFuture("完成");
}
}
1.2 Web請(qǐng)求處理中使用虛擬線程
Spring Boot 3.2+ 啟用虛擬線程后,所有Web請(qǐng)求會(huì)自動(dòng)使用虛擬線程處理,無需額外配置。
驗(yàn)證方式:
@RestController
@RequestMapping("/api/test")
@Slf4j
public class VirtualThreadTestController {
@GetMapping("/virtual-thread")
public Map<String, Object> testVirtualThread() {
Thread currentThread = Thread.currentThread();
Map<String, Object> result = new HashMap<>();
result.put("threadName", currentThread.getName());
result.put("isVirtual", currentThread.isVirtual());
result.put("threadId", currentThread.threadId());
log.info("請(qǐng)求處理線程: {}, 是否為虛擬線程: {}",
currentThread.getName(), currentThread.isVirtual());
return result;
}
}
1.3 定時(shí)任務(wù)中使用虛擬線程
配置類改造:
/**
* 定時(shí)任務(wù)配置(虛擬線程版本)
*
* @author
* @date 2024-10-31
*/
@Slf4j
@Configuration
@EnableScheduling
@ConditionalOnProperty(name = "spring.threads.virtual.enabled", havingValue = "true")
public class VirtualThreadSchedulingConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(taskScheduler());
}
@Bean
public Executor taskScheduler() {
return Executors.newVirtualThreadPerTaskExecutor();
}
}
使用示例:
@Component
@Slf4j
public class ScheduledTaskExample {
/**
* 定時(shí)任務(wù)會(huì)自動(dòng)使用虛擬線程執(zhí)行器
*/
@Scheduled(fixedRate = 5000)
public void scheduledTask() {
log.info("定時(shí)任務(wù)執(zhí)行 - 線程: {}, 是否為虛擬線程: {}",
Thread.currentThread().getName(),
Thread.currentThread().isVirtual());
}
}
方式二:手動(dòng)創(chuàng)建虛擬線程執(zhí)行器
2.1 改進(jìn)現(xiàn)有的VirtualThreadUtils工具類
/**
* 虛擬線程工具類(改進(jìn)版)
*
* @author
* @date 2024-10-31
*/
@Slf4j
@Component
public class VirtualThreadUtils {
private static final ExecutorService EXECUTOR = Executors.newVirtualThreadPerTaskExecutor();
/**
* 執(zhí)行虛擬線程任務(wù)
*
* @param task 任務(wù)
* @return Future
*/
public static Future<?> exeVirtualThread(Runnable task) {
return EXECUTOR.submit(() -> {
try {
log.debug("虛擬線程執(zhí)行任務(wù) - 線程: {}, 是否為虛擬線程: {}",
Thread.currentThread().getName(),
Thread.currentThread().isVirtual());
task.run();
} catch (Exception e) {
log.error("虛擬線程執(zhí)行任務(wù)異常", e);
throw e;
}
});
}
/**
* 執(zhí)行虛擬線程任務(wù)(帶返回值)
*
* @param task 任務(wù)
* @param <T> 返回值類型
* @return Future
*/
public static <T> Future<T> exeVirtualThread(java.util.concurrent.Callable<T> task) {
return EXECUTOR.submit(() -> {
try {
log.debug("虛擬線程執(zhí)行任務(wù) - 線程: {}, 是否為虛擬線程: {}",
Thread.currentThread().getName(),
Thread.currentThread().isVirtual());
return task.call();
} catch (Exception e) {
log.error("虛擬線程執(zhí)行任務(wù)異常", e);
throw e;
}
});
}
@PreDestroy
public void destroy() {
log.info("關(guān)閉虛擬線程執(zhí)行器");
EXECUTOR.shutdown();
try {
if (!EXECUTOR.awaitTermination(60, TimeUnit.SECONDS)) {
EXECUTOR.shutdownNow();
if (!EXECUTOR.awaitTermination(60, TimeUnit.SECONDS)) {
log.error("虛擬線程執(zhí)行器未能正常關(guān)閉");
}
}
} catch (InterruptedException e) {
EXECUTOR.shutdownNow();
Thread.currentThread().interrupt();
}
}
}
2.2 在CompletableFuture中使用虛擬線程
@Service
@Slf4j
public class CompletableFutureExample {
/**
* 使用虛擬線程執(zhí)行器執(zhí)行CompletableFuture
*/
public CompletableFuture<List<String>> processDataAsync() {
Executor virtualExecutor = Executors.newVirtualThreadPerTaskExecutor();
CompletableFuture<List<String>> future = CompletableFuture
.supplyAsync(() -> {
log.info("異步任務(wù)執(zhí)行 - 線程: {}, 是否為虛擬線程: {}",
Thread.currentThread().getName(),
Thread.currentThread().isVirtual());
return fetchData();
}, virtualExecutor)
.thenApplyAsync(data -> {
log.info("處理數(shù)據(jù) - 線程: {}, 是否為虛擬線程: {}",
Thread.currentThread().getName(),
Thread.currentThread().isVirtual());
return processData(data);
}, virtualExecutor);
return future;
}
private List<String> fetchData() {
// 模擬I/O操作
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return Arrays.asList("data1", "data2", "data3");
}
private List<String> processData(List<String> data) {
return data.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
}
}
方式三:為特定場(chǎng)景配置虛擬線程執(zhí)行器
3.1 消息隊(duì)列處理使用虛擬線程
/**
* RabbitMQ虛擬線程配置
*
* @author 往事隨風(fēng)去
* @date 2024-10-31
*/
@Slf4j
@Configuration
@ConditionalOnProperty(name = "spring.threads.virtual.enabled", havingValue = "true")
public class RabbitMQVirtualThreadConfig {
@Bean
public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(
ConnectionFactory connectionFactory) {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
// 使用虛擬線程執(zhí)行器處理消息
factory.setTaskExecutor(new TaskExecutorAdapter(Executors.newVirtualThreadPerTaskExecutor()));
return factory;
}
}
3.2 數(shù)據(jù)庫操作使用虛擬線程
對(duì)于MyBatis等數(shù)據(jù)庫操作,通常不需要特別配置,因?yàn)椋?/p>
- 數(shù)據(jù)庫連接池(如Druid)會(huì)管理連接
- 虛擬線程在執(zhí)行I/O阻塞操作時(shí)會(huì)自動(dòng)釋放平臺(tái)線程
- 可以提高并發(fā)查詢能力
四、最佳實(shí)踐
1. 適用場(chǎng)景
虛擬線程適合:
- I/O密集型任務(wù)(數(shù)據(jù)庫查詢、HTTP請(qǐng)求、文件操作)
- 大量并發(fā)請(qǐng)求處理
- 異步任務(wù)處理
- Web請(qǐng)求處理
虛擬線程不適合:
- CPU密集型任務(wù)(計(jì)算密集型,應(yīng)使用平臺(tái)線程池)
- 需要線程本地存儲(chǔ)(ThreadLocal)的場(chǎng)景(虛擬線程會(huì)頻繁切換)
- 需要精確控制線程數(shù)量的場(chǎng)景
2. 注意事項(xiàng)
2.1 線程本地存儲(chǔ)(ThreadLocal)
虛擬線程會(huì)頻繁切換,ThreadLocal的使用需要注意:
// ? 不推薦:在虛擬線程中使用ThreadLocal存儲(chǔ)大量數(shù)據(jù) ThreadLocal<List<String>> threadLocal = new ThreadLocal<>(); // ? 推薦:使用ScopedValue(Java 21+)或謹(jǐn)慎使用ThreadLocal ScopedValue<String> scopedValue = ScopedValue.newInstance();
2.2 線程池大小配置
啟用虛擬線程后,不需要配置線程池大小,虛擬線程會(huì)自動(dòng)管理:
# ? 不需要配置這些(虛擬線程會(huì)自動(dòng)管理)
server:
tomcat:
threads:
max: 400 # 虛擬線程模式下無效
min-spare: 100 # 虛擬線程模式下無效
2.3 監(jiān)控和調(diào)試
虛擬線程的監(jiān)控需要使用新的API:
@Service
@Slf4j
public class VirtualThreadMonitor {
@Scheduled(fixedRate = 60000)
public void monitorVirtualThreads() {
ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
long[] threadIds = threadBean.getAllThreadIds();
long virtualThreadCount = Arrays.stream(threadIds)
.mapToObj(id -> {
ThreadInfo info = threadBean.getThreadInfo(id);
return info != null ? Thread.ofPlatform().getThreadGroup()
.findThread(id) : null;
})
.filter(Objects::nonNull)
.filter(Thread::isVirtual)
.count();
log.info("虛擬線程數(shù)量: {}", virtualThreadCount);
}
}
3. 性能優(yōu)化建議
3.1 混合使用平臺(tái)線程和虛擬線程
@Configuration
public class HybridThreadConfig {
/**
* CPU密集型任務(wù)使用平臺(tái)線程池
*/
@Bean("cpuIntensiveExecutor")
public Executor cpuIntensiveExecutor() {
int processors = Runtime.getRuntime().availableProcessors();
return Executors.newFixedThreadPool(processors);
}
/**
* I/O密集型任務(wù)使用虛擬線程
*/
@Bean("ioIntensiveExecutor")
public Executor ioIntensiveExecutor() {
return Executors.newVirtualThreadPerTaskExecutor();
}
}
3.2 避免在虛擬線程中執(zhí)行長時(shí)間CPU計(jì)算
@Service
public class TaskService {
@Autowired
@Qualifier("cpuIntensiveExecutor")
private Executor cpuExecutor;
@Autowired
@Qualifier("ioIntensiveExecutor")
private Executor ioExecutor;
public void processTask() {
// I/O操作使用虛擬線程
CompletableFuture<String> data = CompletableFuture
.supplyAsync(this::fetchData, ioExecutor);
// CPU計(jì)算使用平臺(tái)線程
CompletableFuture<String> result = data
.thenApplyAsync(this::heavyComputation, cpuExecutor);
}
}
五、遷移建議
1. 逐步遷移
第一步:啟用虛擬線程
spring: threads: virtual: enabled: true第二步:改造異步配置
- 將線程池配置類改造為使用虛擬線程
- 將
ScheduledTaskCofiguration改造為使用虛擬線程
第三步:測(cè)試和驗(yàn)證
- 驗(yàn)證Web請(qǐng)求是否使用虛擬線程
- 驗(yàn)證異步方法是否使用虛擬線程
- 驗(yàn)證定時(shí)任務(wù)是否使用虛擬線程
第四步:性能測(cè)試
- 對(duì)比啟用虛擬線程前后的性能
- 監(jiān)控內(nèi)存使用情況
- 監(jiān)控線程數(shù)量
2. 回滾方案
如果出現(xiàn)問題,可以快速回滾:
spring:
threads:
virtual:
enabled: false # 禁用虛擬線程,恢復(fù)傳統(tǒng)線程池
六、總結(jié)
虛擬線程在Spring Boot中的正確使用方式:
- 啟用方式:配置
spring.threads.virtual.enabled=true - Web請(qǐng)求:自動(dòng)使用虛擬線程處理(無需額外配置)
- 異步方法:通過
@Async自動(dòng)使用虛擬線程 - 定時(shí)任務(wù):需要配置
SchedulingConfigurer - 手動(dòng)創(chuàng)建:使用
Executors.newVirtualThreadPerTaskExecutor() - 注意事項(xiàng):避免CPU密集型任務(wù),謹(jǐn)慎使用ThreadLocal
通過合理使用虛擬線程,可以顯著提高I/O密集型應(yīng)用的并發(fā)處理能力,減少線程資源消耗。
到此這篇關(guān)于虛擬線程在Spring Boot中的正確使用方式及最佳實(shí)踐的文章就介紹到這了,更多相關(guān)SpringBoot中虛擬線程使用方式內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot項(xiàng)目使用內(nèi)置的單機(jī)任務(wù)調(diào)度功能詳解
這篇文章主要介紹了SpringBoot項(xiàng)目使用內(nèi)置的單機(jī)任務(wù)調(diào)度功能詳解,SpringBoot框架中提供了2個(gè)注解來讓開發(fā)者快速配置來實(shí)現(xiàn)單機(jī)定時(shí)任務(wù)調(diào)度的功能,分別是@EnableScheduling和 @Scheduled,需要的朋友可以參考下2024-01-01
java用重定向方法從文件中讀入或?qū)懭霐?shù)據(jù)
這篇文章主要為大家詳細(xì)介紹了用重定向方法從文件中讀入或?qū)懭霐?shù)據(jù),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-03-03
JAVA編程實(shí)現(xiàn)隨機(jī)生成指定長度的密碼功能【大小寫和數(shù)字組合】
這篇文章主要介紹了JAVA編程實(shí)現(xiàn)隨機(jī)生成指定長度的密碼功能,可生成帶有大小寫和數(shù)字組合的隨機(jī)字符串,需要的朋友可以參考下2017-07-07
jbuilder2006連接sqlserver2000的方法
xp jbuiler2006 連接SQL SERVER2000的問題2008-10-10
SpringBoot項(xiàng)目.gitignore沒生效的解決方案
在 Spring Boot 項(xiàng)目中,.gitignore 文件的作用是告訴 Git 哪些文件或目錄不需要被版本控制,但是我們經(jīng)常會(huì)遇到gitignore失效的問題,所以本文給大家介紹了為什么.gitignore會(huì)失效和解決方案,需要的朋友可以參考下2025-06-06
Java中OutOfMemoryError錯(cuò)誤的原因分析及解決指南
你是否遇到過程序突然崩潰并顯示"OutOfMemoryError"的錯(cuò)誤?別擔(dān)心!這是每個(gè)Java開發(fā)者成長的必經(jīng)之路,本文將用簡單易懂的方式帶你理解這個(gè)常見問題,并提供實(shí)用的解決方案,需要的朋友可以參考下2025-06-06

