當前位置: 妍妍網 > 碼農

SpringBoot + 虛擬執行緒,鳥槍換大炮

2024-06-03碼農

架構師(JiaGouX)

我們都是架構師!
架構未來,你來不來?

什麽是虛擬執行緒

虛擬執行緒是 Java19 開始增加的一個特性,和 Golang 的攜程類似,一個其它語言早就提供的、且如此實用且好用的功能,作為一個 Java 開發者,早就已經望眼欲穿了。

虛擬執行緒和普通執行緒的區別

「虛擬」執行緒,望文生義,它是「假」的,它不直接排程作業系統的執行緒,而是由 JVM 再提供一層執行緒的介面抽象,由普通執行緒排程,即一個普通的作業系統執行緒可以排程成千上萬個虛擬執行緒。

虛擬執行緒比普通執行緒的消耗要小得多得多,在記憶體足夠的情況下,我們甚至可以建立上百萬的虛擬執行緒,這在之前(Java19 以前)是不可能的。

其實如果有用過 akka 的朋友們會發現,其實兩者很相似,只不過使用 akka 是應用程式來處理,而虛擬執行緒是 JVM 來處理,使用上更簡潔且方便。

SpringBoot 使用虛擬執行緒

下面我們會在 SpringBoot 中使用虛擬執行緒,將預設的異步執行緒池和 http 處理執行緒池替換為虛擬執行緒,然後對比虛擬執行緒和普通執行緒的效能差異,你會發現差別就像馬車換高鐵,不是一個時代的東西。

配置

首先我們使用的 Java 版本是 java-20.0.2-oracle,SpringBoot 版本是 3.1.2。

要在 SpringBoot 中使用虛擬執行緒很簡單,增加如下配置即可:


/**
* 配置是用於稍後測試,spring.virtual-thread=true是使用虛擬執行緒,false時還是使用預設的普通執行緒
*/

@Configuration
@ConditionalOnProperty(prefix ="spring", name ="virtual-thread", havingValue ="true")
public classThreadConfig {
@Bean
public AsyncTaskExecutor applicationTaskExecutor() {
returnnewTaskExecutorAdapter(Executors.newVirtualThreadPerTaskExecutor());
}
@Bean
public TomcatProtocolHandlerCustomizer<?>protocolHandlerCustomizer() {
return protocolHandler -> {
protocolHandler.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
};
}
}


@Async 效能對比

我們寫一個異步 service,裏面睡眠 50ms,模擬 MySQL 或 Redis 等 IO 操作:

@Service
public classAsyncService {
/**
*
* @paramcountDownLatch 用於測試
*/

@Async
publicvoiddoSomething(CountDownLatch countDownLatch) throws InterruptedException {
Thread.sleep(50);
countDownLatch.countDown();
}
}

最後測試類,很簡單,就是迴圈呼叫這個方法 10 萬次,計算所有方法執行完成的消耗的時間:

@Test
publicvoidtestAsync() throws InterruptedException {
long start = System.currentTimeMillis();
int n =100000;
CountDownLatch countDownLatch =newCountDownLatch(n);
for (int i =0; i < n; i++) {
asyncService.doSomething(countDownLatch);
}
countDownLatch.await();
long end = System.currentTimeMillis();
System.out.println("耗時:"+ (end - start) +"ms");
}

普通執行緒耗時:678 秒左右,超過 10 分鐘了

虛擬執行緒耗時:3.9 秒!!

朋友們,接近 200 倍的效能差距!!


HTTP 請求效能對比

讓我們再看看 http 請求的對比,簡單寫個 get 請求,裏面什麽也不做,一樣睡 50ms,模擬 IO 操作:

@RequestMapping("/get")
publicObjectget() throws Exception {
Thread.sleep(50);
return"ok";
}

然後我們使用 jmeter 請求介面,500 個並行執行緒,執行 1 萬次,看看效果如何:

「普通執行緒」

可以看到最小用時 50ms,這個沒毛病,介面裏面睡眠了 50ms,但是不管是中位數還是 90/95/99 線都大於 150ms 了,這是因為系統執行緒是一個很昂貴的資源,SpringBoot 中 tomcat 預設的最大連線數應該是 200,在連線池的執行緒被耗盡後,這 200 個執行緒在那幹等 50ms 結束,而剩下的請求也只能等待,無法進行其它的操作。下面再看下虛擬執行緒的表現:

「虛擬執行緒耗時」

可以看到即使是最大耗時,也保持在 100ms 以下,即執行緒等待時間顯著的減少,虛擬執行緒更好的利用了系統資源。

總結

從上面的效能對比來看,虛擬執行緒在效能方面有明顯的優勢,但是要註意的是,我們上面的測試都是讓執行緒等待了 50ms,這是模擬什麽場景?

沒錯,是 IO 密集型場景,即執行緒大部份時間是在等待 IO,這樣虛擬執行緒才可以發揮出它的優勢,如果是 CPU 密集型場景,那麽可能效果並不大。不過我們目前大部份的套用都是 IO 密集型套用較多,比如典型的 WEB 套用,大量的時間在等待網路 IO(DB、緩存、HTTP 等等),使用虛擬執行緒的效果還是非常明顯的。

最後:大部份的公司可能還在用 Java8,但是我想說的是,是時候升級了,跟上時代的腳步吧,朋友們!

如喜歡本文,請點選右上角,把文章分享到朋友圈
如有想了解學習的技術點,請留言給若飛安排分享

因公眾號更改推播規則,請點「在看」並加「星標」 第一時間獲取精彩技術分享

·END·

相關閱讀:

作者:噠噠噠打程式碼

來源:https://juejin.cn/post/7266745788536799247

版權申明:內容來源網路,僅供學習研究,版權歸原創者所有。如有侵權煩請告知,我們會立即刪除並表示歉意。謝謝!

架構師

我們都是架構師!

關註 架構師(JiaGouX),添加「星標」

獲取每天技術幹貨,一起成為牛逼架構師

技術群請 加若飛: 1321113940 進架構師群

投稿、合作、版權等信箱: [email protected]