歡迎訪問我的GitHub
這里分類和匯總了欣宸的全部原創(含配套原始碼):https://github.com/zq2599/blog_demos
《支持JDK19虛擬執行緒的web框架》系列文章鏈接
- 支持JDK19虛擬執行緒的web框架,之一:體驗
- 支持JDK19虛擬執行緒的web框架,之二:完整開發一個支持虛擬執行緒的quarkus應用
- 支持JDK19虛擬執行緒的web框架,之三:觀察運行中的虛擬執行緒
- 支持JDK19虛擬執行緒的web框架,之四:看原始碼,了解quarkus如何支持虛擬執行緒
本篇概覽
- 本篇是《支持JDK19虛擬執行緒的web框架》系列的第五篇,也是全系列的終篇,之前的文章實戰、寫代碼、讀原始碼,想必把大家累壞了,今天咱們開啟聊天模式,暢談虛擬執行緒中的一個關鍵問題,在輕松的氣氛中學習知識,也為整個系列順利收官
關于ThreadLocal
-
既然提到了執行緒,自然繞不開ThreadLocal類,它提供了執行緒本地變數,此變數和一般的變數不同,通過get & set 方法,每個執行緒可以獲取到自己獨立的變數,這個變數實體通常是私有且靜態的,可以存盤與執行緒相關的資訊,如產品id、事務id等,
-
下圖很形象的展現了ThreadLocal:是完全屬于每個執行緒自己的集合

虛擬執行緒中,ThreadLocal的問題
- 既然每個執行緒都可以擁有屬于自己的ThreadLocal物件,那虛擬執行緒的情況又如何呢?
- 虛擬執行緒的特性,使得我們可以在應用代碼中創建成千上萬個虛擬執行緒去執行并發任務,而無需擔心執行緒數量對整體計算資源的負擔,如果每個執行緒都用了ThreadLocal,那會不會出現成千上萬的ThreadLocal物件呢?執行緒是虛擬的,物件可是實實在在的,這樣會增加系統資源消耗,或者影響性能嗎?
- 不過轉念一想,這么明顯的問題,連我都能想到,JDK組織又豈會漏掉?應該是我多慮了吧,憑自己"豐富的經驗",我預測解決方案應該和TLAB(Thread Local Allocation Buffer)類似,為海量虛擬執行緒的ThreadLoacal物件建立映射關系,做到高效管理
- 然而現實很殘酷,臉,被狠狠地抽打,通過Oracle官方博客,知道實際情況真慘...,如下圖,中文注釋是我的解讀,極具悲觀色彩,如果翻譯得不準確請您告知,謝謝

- 對上述內容,個人理解是以下兩點:
- 虛擬執行緒中使用ThreadLocal確實會帶來記憶體問題,現在還無解,連虛擬執行緒自身的工程Loom都在自己代碼中洗掉ThreadLocal的使用,那么我們普通用戶敢用嗎?還是避而遠之吧,在虛擬執行緒中不要用ThreadLocal
- 編號429的JEP,為我們帶來了一個解決方案,一種名為Scoped values的變數,可以在一定范圍(scope)內被訪問,至于這個scope,可以是一個記憶體范圍(例如臨時變數就只能在方法內),另外還有一種范圍被稱為dynamic scope,這個范圍就更加靈活了,不過這個JEP當前的狀態還很早期,如下圖,還在提案階段,這要是跳票了或者被否了,那我博客不就白寫了?就此打住吧,我不能再研究了
- 搞清楚以上問題后,自己的八卦之心就控制不住了:既然虛擬執行緒上的ThreadLocal問題這么嚴重,放眼Java世界的生態這么繁榮,那么多框架和庫,那么...你們說
-
有沒有哪個倒霉蛋掉進這個坑里去?
-
慘不慘?
-
從坑里爬出來沒有?
- 你別說,還真有...
踩坑勇士quarkus
- 這位踩坑勇士,就是貫穿整個《支持JDK19虛擬執行緒的web框架》系列的quarkus,來吧,一起圍觀quarkus踩坑,順便學點知識
- 先看quarkus官方檔案《virtual-threads.adoc》,如下圖

- 我對上述內容的理解:
- quarkus的人發現:傳統執行緒池模式改用虛擬執行緒后,性能提升明顯,但是反應式框架改用虛擬執行緒后的提升并不明顯,而且還會帶來記憶體消耗過大的問題(看過前面ThreadLocal分析的您,此刻應該猜到原因了了,嘿嘿,您猜的沒錯)
- 如果您的應用對記憶體有較嚴要求,quarkus官方建議您繼續堅持(stick)使用反應式框架(這話中透露出濃濃的無可奈何,別催了,搞不定...)
- 接下來官方就要甩鍋了,有趣的是,這次接鍋的并非JDK,而是大名鼎鼎的...Netty
Netty的問題
- 為什么是Netty接鍋呢?
- 首先,Netty使用了Reactor執行緒模型,而Netty Reactor的核心是Event Loop,下圖來自《Netty in Action》,是處理web請求的內部架構圖,

- 那么,應該有多少個EventLoop執行緒呢?下圖是Netty原始碼,默認值是CPU核數的2倍,看得出這是個很保守的數字

- 從上面的架構圖和代碼可以看出,Netty的反應式框架的核心是使用少量執行緒來分發web請求,這樣的結果僅使用了少量執行緒資源就能高效處理事件
- 也正式因為有了執行緒數不多這個前提,在對JSON做序列化處理時,Netty放心的使用了ThreadLocal,畢竟執行緒少,一個4核的CPU也才8個ThreadLocal,毫無壓力
- 而且,為了更加高效,Netty還對ThreadLoacal進行過改造,也就是他們自研的FastThreadLocal
- 然后,時間一天天過去,終于等來了JDK19發布,
- quarkus的反應式web服務模塊底層就是Netty,為了用上虛擬執行緒,他們動手了...咱們腦補一下吧,鋪天蓋地的虛擬執行緒執行緒,鋪天蓋地的FastThreadLocal物件,炸了吧您...Are U OK ?
- 快樂之后,咱們還是要正視這個問題,表面上看是個坑,實際上是兩種設計思路的沖突:
- 虛擬執行緒的特性類似golang的協程,很適合直接拿來處理高并發web請求,為每個請求分配一個虛擬執行緒,邏輯清晰直白,資源消耗又不高,典型的簡單高效
- Netty的反應式模型,核心思路就是用少量執行緒高效分發大量請求,本身就很高效,而且就算優化,執行緒數也不是瓶頸
- 所以,quarkus拎著虛擬執行緒沖到Netty的地盤一陣操作猛如虎,一看結果...唉,扯遠了,來看quarkus官方的解釋吧

- 上圖紅框中那句話很有價值,咱們都能從中領悟到一些東西,我的識訓是:當執行緒數不是系統瓶頸的時候,就別沖動,強行上虛擬執行緒沒用
quarkus強行挽尊
- 既然虛擬執行緒不適合反應式模型,個人認為:那就不妨大大方方的承認Netty的Reactor是優秀的,放棄將虛擬執行緒加入進來,這樣不是挺好么?
- 然而quarkus接下來的操作還是把我嚇到了:既然虛擬執行緒不適合反應式模型?那就想辦法強行讓它適合,下圖就是quarkus的做法:在構建階段,找到創建ThreadLocal的那段代碼,修改它的位元組碼,以此來解決前面的記憶體問題

- 然后我就翻到了上圖提到的那段代碼

- 好奇心驅使,我點開上圖那個NettyCurrentAdaptor去看了下原始碼,當時就一陣頭暈眼花,ASM風格的代碼您能撐多久?試試下圖

- 按照官方的說法,經過他們的優化有百分之八十的提升,終于快要達到之前反應式框架的水平了
- 呃,搞得這么辛苦,也只是快要追上而已,那行,咱不用了行嗎?
- 另外,上面說的優化手段也不是默認開啟的,還要做以下幾步操作
- maven的pom.xml添加以下依賴
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-netty-loom-adaptor</artifactId>
</dependency>
-
編譯構建的時候,增加引數-Dnet.bytebuddy.experimental
-
啟動的時候,增加引數--add-opens java.base/java.lang=ALL-UNNAMED
- 上述操作算,quarkus的手段,我這個草根只能仰望,能開拓自己的見識:原來還可以這樣解決問題
- 但我自己是絕對不敢模仿的,開玩笑,在編輯階段注入代碼,難度太大,并且后面如何維護和交接?
小結
- 至此,咱們壓測做了,代碼寫了,原始碼讀了,八卦也看了,《支持JDK19虛擬執行緒的web框架》系列也到了和您說再見的時候
- 虛擬執行緒很誘人,欣宸和您一樣,迫不及待的想在實際專案中將其用上,實實在在的解決一些問題,正是有了這個目標,才促進了《支持JDK19虛擬執行緒的web框架》系列的誕生,本著為我所用的心態去學習、了解、模仿、鉆研,希望在虛擬執行緒發布的早期階段,該系列文章能豐富您的知識面,為您的決策提供參考資訊,助您在掌握新技術的時候順利搶占先機
- 系列雖然結束了,欣宸原創不會停止,這里永遠是咱們Java愛好者的寧靜港灣,歡迎您的關注
歡迎關注博客園:程式員欣宸
學習路上,你不孤單,欣宸原創一路相伴...
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/533481.html
標籤:Java
下一篇:每日演算法之不用加減乘除做加法
