成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

提升系統(tǒng)吞吐量,詳解JDK21虛擬線程,炸裂

開(kāi)發(fā) 前端
當(dāng)前實(shí)現(xiàn)虛擬線程的一個(gè)限制是,在同步的塊或方法內(nèi)部執(zhí)行阻塞操作會(huì)導(dǎo)致JDK的虛擬線程調(diào)度器阻塞一個(gè)操作系統(tǒng)線程,而在同步的塊或方法外部執(zhí)行阻塞操作則不會(huì)。

環(huán)境:JDK21

1. 虛擬線程簡(jiǎn)介

虛擬線程是輕量級(jí)的線程,可以減少編寫(xiě)、維護(hù)和調(diào)試高吞吐量并發(fā)應(yīng)用程序的工作量。線程是可以調(diào)度的最小處理單元。它與其他類(lèi)似單元并發(fā)運(yùn)行,而且在很大程度上是獨(dú)立運(yùn)行的。它是java.lang.Thread的一個(gè)實(shí)例。線程有兩種,平臺(tái)線程和虛擬線程。

2. 什么是平臺(tái)線程

平臺(tái)線程被實(shí)現(xiàn)為操作系統(tǒng)(OS)線程的薄包裝器。平臺(tái)線程在其底層操作系統(tǒng)線程上運(yùn)行Java代碼,平臺(tái)線程在平臺(tái)線程的整個(gè)生命周期中捕獲其操作系統(tǒng)線程。因此,可用平臺(tái)線程的數(shù)量受限于操作系統(tǒng)線程的數(shù)量。

平臺(tái)線程通常有一個(gè)比較大的線程堆棧和由操作系統(tǒng)維護(hù)的其他資源。它們適用于運(yùn)行所有類(lèi)型的任務(wù),但可能是有限的資源。

3. 什么是虛擬線程

與平臺(tái)線程一樣,虛擬線程也是java.lang.thread的一個(gè)實(shí)例。然而,虛擬線程并沒(méi)有綁定到特定的操作系統(tǒng)線程。虛擬線程仍然在操作系統(tǒng)線程上運(yùn)行代碼。但是,當(dāng)虛擬線程中運(yùn)行的代碼調(diào)用阻塞I/O操作時(shí),Java運(yùn)行時(shí)會(huì)掛起虛擬線程,直到可以恢復(fù)為止。與掛起的虛擬線程相關(guān)聯(lián)的OS線程現(xiàn)在可以自由地執(zhí)行其他虛擬線程的操作。

虛擬線程的實(shí)現(xiàn)方式與虛擬內(nèi)存類(lèi)似。為了模擬大量?jī)?nèi)存,操作系統(tǒng)將大量虛擬地址空間映射到有限的RAM。同樣,為了模擬大量線程,Java運(yùn)行時(shí)將大量虛擬線程映射到少量操作系統(tǒng)線程。

與平臺(tái)線程不同,虛擬線程通常有一個(gè)淺調(diào)用堆棧,只執(zhí)行一個(gè)HTTP客戶(hù)端調(diào)用或一個(gè)JDBC查詢(xún)。盡管虛擬線程支持線程本地變量和可繼承的線程本地變量,但應(yīng)該仔細(xì)考慮使用它們,因?yàn)閱蝹€(gè)JVM可能支持?jǐn)?shù)百萬(wàn)個(gè)虛擬線程。

虛擬線程適用于運(yùn)行大部分時(shí)間被阻塞的任務(wù),這些任務(wù)通常等待I/O操作完成。然而,它們并不適用于長(zhǎng)時(shí)間運(yùn)行的CPU密集型操作。

4. 為什么使用虛擬線程

在高吞吐量并發(fā)應(yīng)用程序中使用虛擬線程,尤其是那些由大量并發(fā)任務(wù)組成、花費(fèi)大量時(shí)間等待的應(yīng)用程序。服務(wù)器應(yīng)用程序是高吞吐量應(yīng)用程序的示例,因?yàn)樗鼈兺ǔL幚碓S多執(zhí)行阻塞I/O操作(如獲取資源)的客戶(hù)端請(qǐng)求。

虛擬線程不是更快的線程;它們運(yùn)行代碼的速度并不比平臺(tái)線程快。它們的存在是為了提供規(guī)模(更高的吞吐量),而不是速度(更低的延遲)。

5. 創(chuàng)建虛擬線程

Thread和Thread.Builder APIs提供了創(chuàng)建平臺(tái)線程和虛擬線程的方法。java.util.concurrent.Executors類(lèi)還定義了創(chuàng)建ExecutorService的方法,該方法為每個(gè)任務(wù)啟動(dòng)一個(gè)新的虛擬線程。

5.1 Thread & Thread.Builder創(chuàng)建虛擬線程

調(diào)用Thread.ofVirtual()方法創(chuàng)建一個(gè)Thread.Builder實(shí)例,用于創(chuàng)建虛擬線程。如下示例:

Thread t= Thread.ofVirtual().start(() -> System.out.println("Hello")) ;
t.join() ;

Thread.Builder接口允許創(chuàng)建具有公共線程屬性(如線程名稱(chēng))的線程。Thread.Builder.OfPlatform子接口創(chuàng)建平臺(tái)線程,而Thread.Builder.OfVirtual創(chuàng)建虛擬線程。

下面的示例使用Thread.Builder接口創(chuàng)建一個(gè)名為T(mén)-VM的虛擬線程,如下示例:

Thread.Builder builder = Thread.ofVirtual().name("T-VM") ;
Runnable task = () -> {
  System.out.println("執(zhí)行任務(wù)") ;
} ;
Thread t = builder.start(task) ;
System.err.printf("線程名稱(chēng): %s%n", t.getName()) ;
t.join() ;

輸出結(jié)果:

執(zhí)行任務(wù)
線程名稱(chēng)T-VM

下面的示例創(chuàng)建并啟動(dòng)兩個(gè)具有Thread.Builder的虛擬線程:

Thread.Builder builder = Thread.ofVirtual().name("vm - worker - ", 0);
Runnable task = () -> {
  System.out.printf("線程ID: %d%n", Thread.currentThread().threadId());
} ;
// 線程 "vm - worker - 0"
Thread t1 = builder.start(task) ;   
t1.join();
System.out.println(t1.getName() + " terminated") ;
// 線程 "vm - worker - 1"
Thread t2 = builder.start(task) ;   
t2.join() ;  
System.out.println(t2.getName() + " terminated") ;

輸出結(jié)果:

線程ID: 21
vm - worker - 0 terminated
線程ID: 24
vm - worker - 1 terminated

以上是通過(guò)Thread.Builder創(chuàng)建虛擬線程的簡(jiǎn)單示例。

5.2 Executors創(chuàng)建虛擬線程

Executors允許將線程管理和創(chuàng)建與應(yīng)用程序的其余部分分離。

ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor() ;
// submit Runnable任務(wù)
Future<?> future = executor.submit(() -> System.out.println("Running thread")) ;
future.get() ;
System.out.println("Task completed") ;

上面示例每當(dāng)調(diào)用ExecutorService.submit(Runnable)時(shí),都會(huì)創(chuàng)建一個(gè)新的虛擬線程并開(kāi)始運(yùn)行該任務(wù)。

6. 虛擬線程調(diào)度

操作系統(tǒng)在平臺(tái)線程運(yùn)行時(shí)進(jìn)行調(diào)度。然而,Java運(yùn)行時(shí)會(huì)在虛擬線程運(yùn)行時(shí)進(jìn)行調(diào)度。當(dāng)Java運(yùn)行時(shí)調(diào)度虛擬線程時(shí),它將虛擬線程分配或掛載到平臺(tái)線程上,然后操作系統(tǒng)像往常一樣調(diào)度該平臺(tái)線程。這個(gè)平臺(tái)線程稱(chēng)為載體(carrier)。運(yùn)行一些代碼后,虛擬線程可以從它的載體卸載。這通常發(fā)生在虛擬線程執(zhí)行阻塞I/O操作時(shí)。當(dāng)一個(gè)虛擬線程從其宿主中卸載后,宿主就處于空閑狀態(tài),這意味著Java運(yùn)行時(shí)調(diào)度器可以在其上分配另一個(gè)虛擬線程。

虛擬線程被綁定到其宿主(平臺(tái)線程)時(shí),在阻塞操作期間無(wú)法將其卸載。虛擬線程在以下情況下會(huì)被綁定:

  • 虛擬線程在同步塊或方法內(nèi)運(yùn)行代碼
  • 虛擬線程運(yùn)行本機(jī)方法或外部函數(shù)

7. 虛擬線程應(yīng)用指南

虛擬線程是由Java運(yùn)行時(shí)而不是操作系統(tǒng)實(shí)現(xiàn)的Java線程。虛擬線程和傳統(tǒng)線程(平臺(tái)線程)的主要區(qū)別在于,可以很容易地在同一個(gè)Java進(jìn)程中運(yùn)行大量甚至數(shù)百萬(wàn)個(gè)活動(dòng)的虛擬線程。正是它們的數(shù)量賦予了虛擬線程強(qiáng)大的能力,通過(guò)允許服務(wù)器并發(fā)處理更多請(qǐng)求,它們可以更高效地運(yùn)行以"thread-per-request"風(fēng)格編寫(xiě)的服務(wù)器應(yīng)用程序,從而提高吞吐量,減少硬件浪費(fèi)。

虛擬線程可以顯著提高以thread-per-request風(fēng)格編寫(xiě)的服務(wù)器的吞吐量,而不是延遲。在這種風(fēng)格中,服務(wù)器在整個(gè)持續(xù)時(shí)間內(nèi)使用一個(gè)線程來(lái)處理每個(gè)傳入的請(qǐng)求。它至少占用一個(gè)線程,因?yàn)樵谔幚韱蝹€(gè)請(qǐng)求時(shí),你可能希望使用更多線程并發(fā)地執(zhí)行某些任務(wù)。

阻塞平臺(tái)線程的代價(jià)是昂貴的,因?yàn)樗鼤?huì)占用線程——這是一種相對(duì)稀缺的資源——而線程并沒(méi)有做很多有意義的工作。因?yàn)樘摂M線程可能很多,所以阻塞它們是廉價(jià)的,也是值得鼓勵(lì)的。因此,應(yīng)該使用簡(jiǎn)單的同步風(fēng)格并使用阻塞I/O API編寫(xiě)代碼。

如下代碼以非阻塞異步風(fēng)格編寫(xiě),不會(huì)從虛擬線程中獲得太多好處。

HttpClient client = ... ;
Executor pool = Executors.newVirtualThreadPerTaskExecutor() ;
CompletableFuture.supplyAsync(() -> {
  HttpRequest request = HttpRequest.newBuilder(URI.create("http://localhost:8088/users/info")).build() ;
  BodyHandler<String> bodyHandler = ... ;
  try {
    return client.send(request , bodyHandler) ;
  }
}, pool)
.thenCompose(url -> getBodyAsync(url, HttpResponse.BodyHandlers.ofString()))
.thenApply(info::findImage)
.thenAccept(this::process)
.exceptionally(t -> { t.printStackTrace(); return null; });

相反,以下代碼以同步風(fēng)格編寫(xiě),并使用簡(jiǎn)單的阻塞IO,將受益匪淺:

try {
   String page = getBody(info.getUrl(), HttpResponse.BodyHandlers.ofString());
   String imageUrl = info.findImage(page);
   byte[] data = getBody(imageUrl, HttpResponse.BodyHandlers.ofByteArray());   
   info.setImageData(data);
   process(info);
}

8. 不要池化虛擬線程

關(guān)于虛擬線程最難理解的是,雖然它們與平臺(tái)線程具有相同的行為,但它們不應(yīng)該表示相同的程序概念。

平臺(tái)線程很少,因此是一種寶貴的資源。寶貴的資源需要管理,管理平臺(tái)線程的最常見(jiàn)方法是使用線程池。接下來(lái)你需要回答的問(wèn)題是,線程池中應(yīng)該有多少個(gè)線程?

但是虛擬線程是很多的,因此每個(gè)線程不應(yīng)該代表某種共享的、池化的資源,而應(yīng)該代表一個(gè)任務(wù)。

將n個(gè)平臺(tái)線程轉(zhuǎn)換為n個(gè)虛擬線程幾乎沒(méi)有好處;相反,需要轉(zhuǎn)換的是任務(wù)。

為了將每個(gè)應(yīng)用任務(wù)表示為一個(gè)線程,不要像下面的例子那樣使用共享線程池:

Future<ResultA> f1 = sharedThreadPoolExecutor.submit(task1);
Future<ResultB> f2 = sharedThreadPoolExecutor.submit(task2);
// ... use futures

相反,使用虛擬線程

try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
   Future<ResultA> f1 = executor.submit(task1);
   Future<ResultB> f2 = executor.submit(task2);
   // ... use futures
}

上面的代碼仍然使用ExecutorService,但從Executors.newVirtualThreadPerTaskExecutor()返回的代碼沒(méi)有使用線程池。相反,它為每個(gè)提交的任務(wù)創(chuàng)建一個(gè)新的虛擬線程。

此外,ExecutorService本身是輕量級(jí)的,我們可以像創(chuàng)建任何簡(jiǎn)單對(duì)象一樣創(chuàng)建一個(gè)新的對(duì)象。這使得我們可以依賴(lài)新添加的ExecutorService#close方法和try-with-resources構(gòu)造。close方法會(huì)在try塊結(jié)束時(shí)隱式調(diào)用,它會(huì)自動(dòng)等待所有提交給ExecutorService的任務(wù)(即由ExecutorService生成的所有虛擬線程)結(jié)束。

對(duì)于扇出場(chǎng)景來(lái)說(shuō),這是一個(gè)特別有用的模式,在這種場(chǎng)景中,同時(shí)執(zhí)行多個(gè)對(duì)不同服務(wù)調(diào)用,如下面的示例所示:

void handle() throws Exception {
  URL url1 = URI.create("http://www.pack.com").toURL() ;
  URL url2 = URI.create("http://www.akf.com").toURL() ;
  try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    var future1 = executor.submit(() -> fetchURL(url1));
    var future2 = executor.submit(() -> fetchURL(url2));
    System.out.printf("result1: %s, result2: %s%n", future1.get(),future2.get()) ;
  }
}


String fetchURL(URL url) throws IOException {
  try (var in = url.openStream()) {
    return new String(in.readAllBytes(), StandardCharsets.UTF_8);
  }
}

你應(yīng)該創(chuàng)建一個(gè)新的虛擬線程,如上所示,即使是小的、短期的并發(fā)任務(wù)。

9. 避免長(zhǎng)時(shí)間頻繁的Pinning

當(dāng)前實(shí)現(xiàn)虛擬線程的一個(gè)限制是,在同步的塊或方法內(nèi)部執(zhí)行阻塞操作會(huì)導(dǎo)致JDK的虛擬線程調(diào)度器阻塞一個(gè)操作系統(tǒng)線程,而在同步的塊或方法外部執(zhí)行阻塞操作則不會(huì)。這種情況稱(chēng)為“Pinning”。如果阻塞操作持續(xù)時(shí)間長(zhǎng)且頻繁,Pinning可能會(huì)對(duì)服務(wù)器的吞吐量產(chǎn)生負(fù)面影響。保護(hù)短期的操作,例如內(nèi)存操作,或者使用同步塊或方法的不頻繁操作,應(yīng)該不會(huì)有任何負(fù)面影響。

對(duì)于長(zhǎng)時(shí)間又頻繁的地方應(yīng)該使用ReentrantLock替換synchronized 。

synchronized(lockObj) {
  frequentIO() ;
}
// 替換為
lock.lock();
try {
  frequentIO() ;
} finally {
  lock.unlock() ;
}


責(zé)任編輯:武曉燕 來(lái)源: Spring全家桶實(shí)戰(zhàn)案例源碼
相關(guān)推薦

2023-11-03 18:23:34

虛擬線程服務(wù)器

2023-10-20 08:12:00

JDK21線程池配置

2024-05-23 16:41:40

2023-12-28 10:49:27

響應(yīng)式編程異步

2023-02-09 08:57:11

Callable異步java

2010-04-14 16:02:09

IDF

2023-09-20 09:07:01

Java 21開(kāi)發(fā)工具包

2024-09-12 15:24:29

2013-04-19 09:45:20

AMPLabHadoopHDFS

2024-09-09 14:12:38

2021-12-26 00:03:27

響應(yīng)式編程異步

2025-05-09 02:00:00

代碼接口吞吐量

2024-01-19 13:42:00

模型訓(xùn)練

2024-12-13 13:58:53

2025-03-04 08:52:21

2025-06-13 09:12:28

2024-11-14 15:00:00

線程架構(gòu)吞吐量

2024-09-14 11:31:27

@AsyncSpring異步

2023-11-07 15:11:46

Kafka技巧

2023-08-03 14:18:29

Rust阻塞函數(shù)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 国产精品1区2区3区 欧美 中文字幕 | 欧美毛片免费观看 | 国产亚洲一区二区三区 | 日日操夜夜操天天操 | 久久精品中文 | 久久精品久久综合 | www.中文字幕.com | 中文字幕国产高清 | 中文字幕亚洲视频 | 在线精品亚洲欧美日韩国产 | 一区二区三区国产好 | 亚洲精品91| 日韩有码一区 | 久久久国产精品一区 | 免费成人av | www.国产精| 成在线人视频免费视频 | 久久精品一 | 一区二区三区电影在线观看 | 九九久久精品 | 综合久久网 | 国产色婷婷久久99精品91 | 老司机成人在线 | 欧美1页 | 成人国产a | 日本精a在线观看 | 99re热精品视频国产免费 | 亚洲黄色av | 在线中文一区 | 激情五月激情综合网 | 狠狠的日| 伊人久久免费 | 国产精品美女久久久久久不卡 | 日本午夜在线视频 | 男人的天堂中文字幕 | 成人国产网站 | 亚洲一区二区在线 | 搞av.com | 日本久久视频 | 亚洲人成人一区二区在线观看 | 伊人网一区 |