您現在的位置是:首頁 > 綜合

Java19 帶來的虛擬執行緒是怎樣玩出花提升十倍效能的

  • 由 程式設計師子悠 發表于 綜合
  • 2023-01-08
簡介println(count +“ os thread”)

虛擬執行緒作用大嗎

我們都知道一個

Java

服務其實是一個程序,當這個服務遇到高併發場景的時候我們往往會考慮使用執行緒池來提高效能,

JDK

中就自帶執行緒池,關於

JDK

的執行緒池感興趣可以去看一阿粉之前的文章

聊聊面試中的 Java 執行緒池

。今天阿粉想跟大家聊的時候

Java19

中提到的虛擬執行緒

virtual threads

基本概念

我們都知道

Java

中的執行緒跟作業系統的核心執行緒是一對一的,

Java

執行緒的排程其實是依賴作業系統的核心執行緒的,這就導致了我們的執行緒切換和執行就需要進行上下文切換以及消耗大量的系統資源,同時我們也知道機器的資源是昂貴的並且也是有限的,我們不能也無法肆無忌憚的建立執行緒,因此執行緒往往會成為我們系統的瓶頸。

為了解決這個問題,

Java19

中提出了一種虛擬執行緒的概念,為了區別,之前的執行緒被稱為平臺執行緒。要注意虛擬執行緒並不是用來直接取代平臺執行緒的,虛擬執行緒是建議在平臺執行緒之上的,一個平臺執行緒可以對應多個虛擬執行緒,同時一個平臺執行緒還是一一對應核心執行緒,因此上面的架構就變成了如下,一個

VT

代表一個虛擬執行緒。

Java19 帶來的虛擬執行緒是怎樣玩出花提升十倍效能的

如果有小夥伴對

GO

語言比較熟悉的話,就會想到

Java

中的虛擬執行緒跟

GO

中的

Goroutines

是很類似的,確實是這樣,所以說語言都是相通的。

舉個栗子

這裡我們透過分別使用平臺執行緒以及虛擬執行緒來測試一個

case

看看兩者的耗時和效能是怎樣的,測試分如下幾步,我們依次來看一下。

注意下面的測試程式碼都是在 Java19 的版本中執行的

平臺執行緒方式

我們透過

JDK

自帶的執行緒池

Executors。newCachedThreadPool()

來建立執行緒池,並執行一定資料任務,任務的數量我們透過入參來控制,方便後續透過主函式呼叫。

public

static

void

platformThread

int

size)

{

long

l = System。currentTimeMillis();

try

var

executor = Executors。newCachedThreadPool()) {

IntStream。range(

0

, size)。forEach(i -> {

executor。submit(() -> {

Thread。sleep(Duration。ofSeconds(

1

));

//System。out。println(i);

return

i;

});

});

}

System。out。printf(

“elapsed time: %dms\n”

, System。currentTimeMillis() - l);

}

虛擬執行緒的方式

虛擬執行緒的程式碼跟上面的程式碼十分相似,程式碼如下。可以看到,在程式碼層面上跟上面唯一的區別就是

Executors。newCachedThreadPool()

這一行變成了

Executors。newVirtualThreadPerTaskExecutor()

即代表建立的虛擬執行緒。

public

static

void

virThread

int

size)

{

long

l = System。currentTimeMillis();

try

var

executor = Executors。newVirtualThreadPerTaskExecutor()) {

IntStream。range(

0

, size)。forEach(i -> {

executor。submit(() -> {

Thread。sleep(Duration。ofSeconds(

1

));

//System。out。println(i);

return

i;

});

});

}

System。out。printf(

“elapsed time: %dms\n”

, System。currentTimeMillis() - l);

}

監控執行的執行緒

上面的兩個方法都是都是建立執行緒池用來提交任務的,但是位於具體建立了多少個執行緒我們是不知道的,所以我們還需要透過下面的程式碼來監控。

public

static

void

main

(String[] args)

{

ScheduledExecutorService scheduledExecutorService = Executors。newScheduledThreadPool(

1

);

scheduledExecutorService。scheduleAtFixedRate(() -> {

ThreadMXBean threadBean = ManagementFactory。getThreadMXBean();

ThreadInfo[] threadInfo = threadBean。dumpAllThreads(

false

false

);

long

count = Arrays。stream(threadInfo)。count();

System。out。println(count +

“ os thread”

);

},

1

1

, TimeUnit。SECONDS);

int

size =

100000

// platformThread(size);

virThread(size);

}

透過另一個執行緒池開啟一個執行緒資訊監控的執行緒,每秒鐘輸出一次當前的執行執行緒數。這裡注意,如果上面的程式碼在

IDEA

中提示報錯,找不到類,如下所示,我們可以將滑鼠放上去進行修復。

也可以手動在設定中的編譯器》

Java

編譯器這裡給自己的模組增加一個編譯引數

-parameters ——add-modules java。management ——enable-preview

Java19 帶來的虛擬執行緒是怎樣玩出花提升十倍效能的

執行

上面的三段組合在一起就是一個完整的

case

,如果這個時候如果上面的程式碼都正常,在執行的時候不出意外會出現下面的錯誤,

Java19 帶來的虛擬執行緒是怎樣玩出花提升十倍效能的

這裡是因為當前

Java19

中的虛擬執行緒特性還處於預覽階段,不能直接使用,我們需要在啟動引數上面配置

——enable-preview

引數,才能正常測試,如下所示,不同版本的

IDEA

可能顯示的位置不一樣,但是都是配置

VM

引數,找一下就好了。

Java19 帶來的虛擬執行緒是怎樣玩出花提升十倍效能的

配置好了過後再次執行就可以得到如下的結果,可以看到在

siz

e 大小為 100000 的情況下,虛擬執行緒只建立了 12 個平臺執行緒,並且只在 2523 ms 就完成了整個任務。

但是當我們執行平臺執行緒的方法的時候會發現,同樣的

size

的情況下,平臺執行緒建立了好幾千個,而且還會觸發

OOM

,因為作業系統的資源已經被耗盡了,由此可見虛擬執行緒的效能要遠遠高於平臺執行緒。YYDS!

為了避免

OOM 我們

也可以將程式碼中的

Executors。newCachedThreadPool()

方法,改成

Executors。newFixedThreadPool(xxx)

,這樣雖然可以避免大量建立執行緒導致

OOM

,但是任務執行的時長就會消耗更長,阿粉這邊測試在

size

為 10000 的情況下,配置 500 個執行緒的時候,總共花費了 20276 ms,在資料量小十倍的情況下耗時卻增長十倍。效能可想而知,感興趣的小夥伴可以自己嘗試一下。

Top