當前位置: 妍妍網 > 碼農

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

2024-06-23碼農

來源:juejin.cn/post/7266745788536799247

👉 歡迎 ,你將獲得: 專屬的計畫實戰 / 1v1 提問 / Java 學習路線 / 學習打卡 / 每月贈書 / 社群討論

  • 新計畫: 【從零手擼:仿小紅書(微服務架構)】 正在持續爆肝中,基於 Spring Cloud Alibaba + Spring Boot 3.x + JDK 17..., ;

  • 【從零手擼:前後端分離部落格計畫(全棧開發)】 2期已完結,演示連結: http://116.62.199.48/ ;

  • 截止目前, 累計輸出 47w+ 字,講解圖 2090+ 張,還在持續爆肝中.. 後續還會上新更多計畫,目標是將 Java 領域典型的計畫都整一波,如秒殺系統, 線上商城, IM 即時通訊,Spring Cloud Alibaba 等等,

  • 什麽是虛擬執行緒

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

  • SpringBoot使用虛擬執行緒

  • 配置

  • @Async效能對比

  • HTTP請求效能對比

  • 總結

  • 什麽是虛擬執行緒

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

    @Async效能對比

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

    @Service
    public class AsyncService {
    /**

    * @param countDownLatch 用於測試
    */
    @Async
    public void doSomething(CountDownLatch countDownLatch) throws InterruptedException {
    Thread.sleep(50);
    countDownLatch.countDown();
    }
    }

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

    @Test
    public void testAsync() 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,但是我想說的是,是時候升級了,跟上時代的腳步吧,朋友們!

    👉 歡迎 ,你將獲得: 專屬的計畫實戰 / 1v1 提問 / Java 學習路線 / 學習打卡 / 每月贈書 / 社群討論

  • 新計畫: 【從零手擼:仿小紅書(微服務架構)】 正在持續爆肝中,基於 Spring Cloud Alibaba + Spring Boot 3.x + JDK 17..., ;

  • 【從零手擼:前後端分離部落格計畫(全棧開發)】 2期已完結,演示連結: http://116.62.199.48/ ;

  • 截止目前, 累計輸出 47w+ 字,講解圖 2090+ 張,還在持續爆肝中.. 後續還會上新更多計畫,目標是將 Java 領域典型的計畫都整一波,如秒殺系統, 線上商城, IM 即時通訊,Spring Cloud Alibaba 等等,


    1. 

    2. 

    3. 

    4. 

    最近面試BAT,整理一份面試資料Java面試BATJ通關手冊,覆蓋了Java核心技術、JVM、Java並行、SSM、微服務、資料庫、數據結構等等。

    獲取方式:點「在看」,關註公眾號並回復 Java 領取,更多內容陸續奉上。

    PS:因公眾號平台更改了推播規則,如果不想錯過內容,記得讀完點一下在看,加個星標,這樣每次新文章推播才會第一時間出現在你的訂閱列表裏。

    「在看」支持小哈呀,謝謝啦