並發編程的一點思考

並發編程可以總結為3個核心問題:

  • 分工:指的是如何高效的拆解任務並分配給執行緒
  • 同步:指的是執行緒之間如何協作
  • 互斥:則是保證同一時刻只允許一個執行緒訪問共享資源

JavaSDK並發包很大部分都是按照這三個維度組織的:
並發編程

進一步的,個人理解,三大核心問題又可以聚焦於2大點:

  • 管程(synchronized、SDK里的各種Lock)解決同步互斥問題
  • 執行緒池(CompletableFuture、Fork/Join)實現分工並行

抓住這2點,就可以由點及線,把整個並發編程體系串聯起來。個人理解,其實執行緒池部分嚴格說來應該屬於並行編程。

ps.並發 VS 並行 ?

  • 並發,指的是某一時間段內,單核CPU多任務處理,因為執行緒調度切換使得看起來是同時執行多任務一樣,實際上某一時間只有一個執行緒運行。
  • 並行,指的是多CPU情況下,同一時間點多個執行緒同時運行。

管程

管程是一把解決並發問題的萬能鑰匙。所謂管程,指的是管理共享變數以及對共享變數的操作過程,讓他們支援並發。翻譯為 Java 領域的語言,就是管理類的成員變數和成員方法,讓這個類是執行緒安全的。
管程

在管程模型里,共享變數和對共享變數的操作是被封裝起來的,當多個執行緒同時試圖進入管程內部時,只允許一個執行緒進入,其他執行緒則在入口等待隊列中等待。 —-解決了互斥
條件變數和等待隊列的作用—-解決執行緒同步問題

  • 多個執行緒同時試圖進入管程內部時,只允許一個執行緒進入,其他執行緒則在入口等待隊列中等待(處於阻塞狀態)。
  • T1執行緒進入後,檢查條件變數A,如滿足,則執行。如不滿足,T1 需要調用 A.wait()進入條件變數A的等待隊列(釋放了鎖,處於無限等待狀態/有限時等待狀態),進入之後,允許其他執行緒進入管程。
  • 另外一個執行緒 T2 執行成功之後,如果條件A對於執行緒 T1 來說已經滿足了,此時執行緒 T2 要通知 T1,執行緒 T2 需要調用 A.notifyAll() 來喚醒 A 等待隊列中的執行緒,當這些執行緒得到通知後,會從等待隊列裡面出來,但是出來之後不是馬上執行,而是重新進入到入口等待隊列裡面去競爭鎖,執行緒是否競爭到鎖與執行緒在等待隊列中的先後位置是否相關,又產生了非公平鎖和公平鎖之區分。

java語言內置的管程(synchronized)對 MESA 管程模型進行了精簡。MESA 模型中,條件變數可以有多個,synchronized只有一個條件變數,只支援非公平鎖。
正是由於synchronized的局限性,才有了JUC包中的各種lock,JUC包中的AQS就是對MESA管程的實現,然後基於AQS延伸開發出了各種lock和並發工具類,其核心思想的源頭都是管程!

執行緒池

降低延遲,提高吞吐量

執行緒池基本原理:美團的這篇文章講的很好—Java執行緒池實現原理及其在美團業務中的實踐

多執行緒優化性能的目標說白了就是2點,降低延遲,提高吞吐量,具體做法就是串改並,串列轉換成並行的過程中,一定會涉及到非同步化。非同步化,是並行方案得以實施的基礎。

  • 簡單的並行任務—————————————–> 執行緒池 +Future

  • 任務之間有聚合關係(AND 聚合/OR 聚合)——————–> CompletableFuture (強烈推薦使用!!!)

  • 分治 ————————————————-> Fork/Join (適合處理一些特殊場景,並行計算 海量數據處理)

一些執行緒池的騷操作:
美團動態執行緒池
按某個維度順序執行任務的執行緒池
記憶體安全的阻塞隊列
原生執行緒池拒絕策略的bug