當前位置: 妍妍網 > 碼農

SpringBoot + 虛擬執行緒,效能炸裂!

2024-05-28碼農

點選「 IT碼徒 」, 關註,置頂 公眾號

每日技術幹貨,第一時間送達!

什麽是虛擬執行緒

虛擬執行緒是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(){
returnnew TaskExecutorAdapter(Executors.newVirtualThreadPerTaskExecutor());
}
@Bean
public TomcatProtocolHandlerCustomizer<?> protocolHandlerCustomizer() {
return protocolHandler -> {
protocolHandler.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
};
}
}

@Async效能對比

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

@Service
public classAsyncService{
/**

@param countDownLatch 用於測試
*/

@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 = new CountDownLatch(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")
public Object get()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,但是我想說的是,是時候升級了,跟上時代的腳步吧,朋友們!

來源: juejin.cn/post/7266745788536799247

END

PS:防止找不到本篇文章,可以收藏點贊,方便翻閱尋找哦。

往期推薦