欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

SpringBoot3中使用虛擬線程的詳細(xì)過(guò)程

 更新時(shí)間:2025年06月06日 11:13:28   作者:颯颯的宇y  
虛擬線程主要解決的問(wèn)題是減少I/O密集型任務(wù)的I/O阻塞,這篇文章主要介紹了SpringBoot3中使用虛擬線程的詳細(xì)過(guò)程,需要的朋友可以參考下

Java21、SpringBoot3中使用虛擬線程

引言

最近有讀取文件中數(shù)據(jù)的需求,且數(shù)據(jù)量百萬(wàn)至千萬(wàn),普通的多線程讀取方式還是很慢。遂想到Java21虛擬線程,在網(wǎng)上學(xué)習(xí)了一圈,簡(jiǎn)單的在這里記一個(gè)筆記,方便日后查找。

以下內(nèi)容是個(gè)人理解,注解辨別。

一、什么是虛擬線程,有什么用

1、虛擬線程是Java19提出來(lái)的一個(gè)概念,Java19提供特性預(yù)覽,開放實(shí)裝是Java21(2023年9月),目前來(lái)說(shuō)(2024年3月),還是一個(gè)比較新的特性,對(duì)于一些不是很常見的庫(kù)可能尚未適配。

2、虛擬線程主要解決的問(wèn)題是減少I/O密集型任務(wù)I/O阻塞。傳統(tǒng)的多線程在處理I/O的時(shí)候,例如某個(gè)線程在處理某個(gè)任務(wù),如果遇到I/O例如網(wǎng)絡(luò)通信、文件讀取,受限于網(wǎng)絡(luò)速度、機(jī)器的硬盤I/O速度等,這個(gè)線程會(huì)阻塞等待I/O的完成,然后再繼續(xù)往下執(zhí)行任務(wù)。這就帶來(lái)了一個(gè)問(wèn)題,由于存在I/O過(guò)程,這個(gè)線程它不干活了,就在那里阻塞干等I/O完成,偷懶了一會(huì)兒,導(dǎo)致了CPU的利用率其實(shí)沒有達(dá)到理想狀態(tài)。

3、而虛擬線程則是在線程遇到I/O阻塞時(shí),會(huì)放暫時(shí)放棄等待去做其他事,等I/O完成它才會(huì)回來(lái)繼續(xù)執(zhí)行任務(wù),無(wú)疑這種方式提高了CPU的利用率,讓它無(wú)法再偷懶。
虛擬線程是由JVM來(lái)管理的,不由系統(tǒng)管理,并且它十分輕量,虛擬線程之間的切換開銷十分的小,所以你甚至可以開啟上百萬(wàn)個(gè)虛擬線程。

4、虛擬線程并沒有增加實(shí)際的CPU可用線程,而是增加了線程的利用率,所以在面對(duì)CPU密集型任務(wù)時(shí),如數(shù)學(xué)計(jì)算等,它與傳統(tǒng)的線程沒有區(qū)別,因?yàn)?code>CPU密集型任務(wù)不存在大量的I/O等待。
如果你的需求中存在大量I/O等待導(dǎo)致性能瓶頸,那么可以考慮使用虛擬線程。

二、JavaSE中使用虛擬線程

官方對(duì)虛擬線程做了很多封裝,你可以很簡(jiǎn)單的使用它

Thread.ofVirtual().start(() -> {
	//異步任務(wù),這里的參數(shù)不用lambda表達(dá)式的話要傳一個(gè)實(shí)現(xiàn)Runnable的類
}); //開啟一個(gè)虛擬線程

部分源代碼:

public static Builder.OfVirtual ofVirtual() {
        return new ThreadBuilders.VirtualThreadBuilder();
}
public Thread start(Runnable task) {
            Thread thread = unstarted(task);
            thread.start();
            return thread;
}

同時(shí),官方還提供了使用平臺(tái)線程(真實(shí)線程)的方法:

Thread.ofPlatform().start(() -> {
	//異步任務(wù),這里的參數(shù)不用lambda表達(dá)式的話要傳一個(gè)實(shí)現(xiàn)Runnable的類
}); //開啟一個(gè)平臺(tái)線程

當(dāng)然,在實(shí)際使用中,我們不可能只使用一個(gè)虛擬線程,所以官方提供了一個(gè)類似于線程池的方法:

var executors = Executors.newVirtualThreadPerTaskExecutor() //開啟一個(gè)“虛擬線程池”
executors.submit(() -> {
	//異步任務(wù),這里的參數(shù)不用lambda表達(dá)式的話要傳一個(gè)實(shí)現(xiàn)Runnable的類
}); //向“虛擬線程池”中提交一個(gè)任務(wù)

說(shuō)明一下:
首先var關(guān)鍵字是Java10引入的,類似于C++中的auto,它可以自動(dòng)類型推斷,就是說(shuō)我們以后可以不用寫類似 ArrayList<String> list = new ArrayList<>(); 這樣的代碼了,直接 var list = new ArrayList<String>(); 編譯器會(huì)自己類型推斷,但是這么寫你得注意變量名,不然讀起來(lái)會(huì)很麻煩。還有var關(guān)鍵字只能用于局部變量,成員變量、形參等都不行。

這種方式用虛擬線程,JVM會(huì)自動(dòng)利用機(jī)器可用的真實(shí)線程來(lái)跑這些虛擬線程,不用額外配置

使用“虛擬線程池”的時(shí)候最好搭配try-with-resource,以達(dá)到資源的自動(dòng)釋放:

try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
	for (int i = 0; i < 1000000; i++) {
	    int finalI = i;
	    var future = executor.submit(() -> {
	        System.out.println("任務(wù) " + finalI +" 運(yùn)行在 " + Thread.currentThread());
	        TimeUnit.SECONDS.sleep(3);//模擬阻塞
	        // 執(zhí)行其他操作
	        return "ok";
	    });
	}
}

你可以在下方使用 Future.get() 的方式來(lái)等待線程執(zhí)行完畢

以上代碼執(zhí)行結(jié)果:

...
任務(wù) 992 運(yùn)行在 VirtualThread[#1038]/runnable@ForkJoinPool-1-worker-12
任務(wù) 578 運(yùn)行在 VirtualThread[#622]/runnable@ForkJoinPool-1-worker-6
任務(wù) 802 運(yùn)行在 VirtualThread[#848]/runnable@ForkJoinPool-1-worker-4
任務(wù) 997 運(yùn)行在 VirtualThread[#1043]/runnable@ForkJoinPool-1-worker-13
...

我們可以看到線程名成為了 VirtualThread ,它們運(yùn)行在不同的真實(shí)線程上 worker-13 、 worker-4

三、SpringBoot中使用虛擬線程

在SpringBoot中使用虛擬線程要求SpringBoot的版本最低是 3.x ,JDK的版本不能低于21

*** 注意,暫時(shí)不要用Undertow作為Servlet容器來(lái)使用虛擬線程,可能會(huì)有內(nèi)存溢出等問(wèn)題,你的內(nèi)存占用會(huì)飆得老高,SpringBoot官方目前說(shuō)的是Undertow的問(wèn)題,另外還補(bǔ)充說(shuō)如果這個(gè)問(wèn)題沒能得到解決的話,未來(lái)可能移除對(duì)Undertow的虛擬線程支持,所以暫時(shí)別在生產(chǎn)環(huán)境中使用。官方GitHub issues 原文地址

1、使用

使用Jdk21,Maven引入SpringBoot3.x

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>3.2.1</version>
</dependency>

新建一個(gè)配置類,添加兩個(gè)Bean:

@Configuration
public class Config {
    @Bean
    public AsyncTaskExecutor asyncTaskExecutor(){
        return new TaskExecutorAdapter(Executors.newVirtualThreadPerTaskExecutor());
    }
    @Bean
    public TomcatProtocolHandlerCustomizer<?> protocolHandlerVirtualThreadExecutorCustomizer() {
        return protocolHandler -> protocolHandler.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
    }
}

說(shuō)明一下,第一個(gè)Bean AsyncTaskExecutor 是為了讓SpringBoot在你使用 @Async 注解的時(shí)候用虛擬線程執(zhí)行你的 @Async 任務(wù)。

第二個(gè)Bean TomcatProtocolHandlerCustomizer 是為了讓Tomcat用虛擬線程來(lái)接請(qǐng)求的。我們可以測(cè)試一下,寫一個(gè)Controller和一個(gè)Service:

@RestController
@RequestMapping("/base")
public class TestController {
    @Resource
    private TestService service;
    @GetMapping("/test")
    public String test(){
        System.out.println("---> " + Thread.currentThread());
        service.test();
        return "ok";
    }
}
@EnableAsync
@Service
public class TestService {
    @Async
    public void test(){
        System.out.println("================> " + Thread.currentThread());
    }
}

運(yùn)行這個(gè)服務(wù),訪問(wèn) localhost:8080/base/test 接口的時(shí)候,會(huì)打印:

---> VirtualThread[#65]/runnable@ForkJoinPool-1-worker-1
================> VirtualThread[#71]/runnable@ForkJoinPool-1-worker-4

發(fā)現(xiàn)都是虛擬線程,我們?nèi)偛判陆ǖ呐渲妙愔凶⑨尩粝旅娴?TomcatProtocolHandlerCustomizer 這個(gè)Bean,重啟服務(wù)再訪問(wèn)接口:

---> Thread[#61,http-nio-8080-exec-1,5,main]
================> VirtualThread[#78]/runnable@ForkJoinPool-1-worker-1

會(huì)發(fā)現(xiàn)接收請(qǐng)求的線程不是虛擬線程了,同樣的,注釋掉上面的 AsyncTaskExecutor 那么我們用 @Async 注解的方法也是不使用虛擬線程了。

如果你想提高請(qǐng)求處理量的話,就注入下面這個(gè)Bean,只是想使用虛擬線程做其他事的話,下面這個(gè)Bean是可選的。

2、線程池問(wèn)題

如果你的打印內(nèi)容出現(xiàn):

.s.a.AnnotationAsyncExecutionInterceptor : More than one TaskExecutor bean found within the context,
 and none is named 'taskExecutor'. Mark one of them as primary or name it 'taskExecutor' (possibly as an alias) 
 in order to use it for async processing: [asyncTaskExecutor, taskScheduler]

或者你打印的線程信息類似:

Thread[#129,SimpleAsyncTaskExecutor-2,5,VirtualThreads]

第一個(gè)警告的意思是在上下文中找到多個(gè)TaskExecutor bean,但沒有一個(gè)名稱為“TaskExecutor”。將其中一個(gè)標(biāo)記為主任務(wù)或?qū)⑵涿麨?ldquo;taskExecutor”(可能是別名),以便將其用于異步處理:[asyncTaskExecutor,taskScheduler]
問(wèn)題是項(xiàng)目中引入的線程池過(guò)多,Spring容器不知道用哪一個(gè),我們只需要在AsyncTaskExecutor這個(gè)Bean上添加一個(gè)@Primary將我們?cè)O(shè)置的線程池設(shè)置為主線程池就行了:

@Bean
@Primary // 將我們?cè)O(shè)置的虛擬線程池作為主線程池
public AsyncTaskExecutor asyncTaskExecutor(){
	return new TaskExecutorAdapter(Executors.newVirtualThreadPerTaskExecutor());
}

四、總結(jié)

目前我沒有做具體的性能測(cè)試,有興趣的可以做做壓測(cè)。后續(xù)我做了測(cè)試的話會(huì)更新這篇博客。
虛擬線程目前來(lái)說(shuō)是一個(gè)很新的特性,可能會(huì)有很多問(wèn)題尚未發(fā)現(xiàn),使用要謹(jǐn)慎。
此外,CPU密集型任務(wù)目前來(lái)說(shuō)就不建議用虛擬線程了,性能沒提升。

參考文獻(xiàn)

1、簡(jiǎn)單記錄下 Spring Boot 使用虛擬線程Virtual Threads(Java的協(xié)程)的方法
2、Java21手冊(cè)(一):虛擬線程 Virtual Threads

到此這篇關(guān)于SpringBoot3中使用虛擬線程的文章就介紹到這了,更多相關(guān)SpringBoot使用虛擬線程內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 老生常談Scanner的基本用法

    老生常談Scanner的基本用法

    下面小編就為大家?guī)?lái)一篇老生常談Scanner的基本用法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-07-07
  • Idea啟動(dòng)多個(gè)SpringBoot項(xiàng)目的3種最新方案

    Idea啟動(dòng)多個(gè)SpringBoot項(xiàng)目的3種最新方案

    SpringBoot自帶Tomcat,直接運(yùn)行main方法里面的SpringApplication.run即可,并且訪問(wèn)時(shí)不需要帶項(xiàng)目名,這篇文章主要介紹了Idea啟動(dòng)多個(gè)SpringBoot項(xiàng)目的3種方案,需要的朋友可以參考下
    2023-02-02
  • 簡(jiǎn)單了解Spring Boot及idea整合jsp過(guò)程解析

    簡(jiǎn)單了解Spring Boot及idea整合jsp過(guò)程解析

    這篇文章主要介紹了簡(jiǎn)單了解Spring Boot及idea整合jsp過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-11-11
  • 排序算法圖解之Java冒泡排序及優(yōu)化

    排序算法圖解之Java冒泡排序及優(yōu)化

    冒泡排序即通過(guò)對(duì)待排序的序列從前往后,依次比較相鄰元素的值,若發(fā)現(xiàn)逆序則交換位置,使較大的元素逐漸移動(dòng)到后部。本文通過(guò)圖片和示例介紹了冒泡排序的實(shí)現(xiàn)及優(yōu)化,需要的可以參考一下
    2022-11-11
  • Java聊天室之實(shí)現(xiàn)聊天室服務(wù)端功能

    Java聊天室之實(shí)現(xiàn)聊天室服務(wù)端功能

    這篇文章主要為大家詳細(xì)介紹了Java簡(jiǎn)易聊天室之實(shí)現(xiàn)聊天室服務(wù)端功能,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,需要的可以了解一下
    2022-10-10
  • java高并發(fā)ScheduledThreadPoolExecutor與Timer區(qū)別

    java高并發(fā)ScheduledThreadPoolExecutor與Timer區(qū)別

    這篇文章主要為大家介紹了java高并發(fā)ScheduledThreadPoolExecutor與Timer區(qū)別,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-10-10
  • 支撐Java NIO與NodeJS的底層技術(shù)

    支撐Java NIO與NodeJS的底層技術(shù)

    這篇文章主要為大家詳細(xì)介紹了支撐Java NIO與NodeJS的底層技術(shù),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2016-09-09
  • Springboot2.7+Minio8 實(shí)現(xiàn)大文件分片上傳

    Springboot2.7+Minio8 實(shí)現(xiàn)大文件分片上傳

    本文主要介紹了Springboot2.7+Minio8 實(shí)現(xiàn)大文件分片上傳,通過(guò)文件切片上傳,我們能夠提高文件上傳的速度,優(yōu)化用戶體驗(yàn),具有一定的參考價(jià)值,感興趣的可以了解一下
    2023-12-12
  • 深入理解Java設(shè)計(jì)模式之適配器模式

    深入理解Java設(shè)計(jì)模式之適配器模式

    這篇文章主要介紹了JAVA設(shè)計(jì)模式之適配器模式的的相關(guān)資料,文中示例代碼非常詳細(xì),供大家參考和學(xué)習(xí),感興趣的朋友可以了解
    2021-11-11
  • Spring計(jì)時(shí)器StopWatch使用示例

    Spring計(jì)時(shí)器StopWatch使用示例

    這篇文章主要介紹了Spring計(jì)時(shí)器StopWatch使用示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-05-05

最新評(píng)論