1. 程式人生 > >【Java併發基礎】併發程式設計領域的三個問題:分工、同步和互斥

【Java併發基礎】併發程式設計領域的三個問題:分工、同步和互斥

前言

可以將Java併發程式設計抽象為三個核心問題:分工、同步和互斥。
這三個問題的產生源自對效能的需求。最初時,為提高計算機的效率,當IO在等待時不讓CPU空閒,於是就出現了分時作業系統也就出現了併發。後來,多核CPU出現,不同的任務可以同時獨立執行,於是就出現了並行【分工】。有了分工後,效率得到了很大的提升,但是為了更合理的安排以及控制任務的進行,就需要讓程序之間可以通訊【同步】,讓彼此知道進度的執行。分工進行提高了效率,但是卻帶來了多執行緒訪問共享資源會衝突的問題。於是對共享資源的訪問又需要序列化。所以,依據現實世界的做法設計了鎖等機制來使得多執行緒【互斥】訪問共享資源。

分工(效能)

分工的主要工作是:如何高效拆解任務並分配給執行緒。   

Java SDK併發包中的ExecutorFork/JoinFuture本質上都是分工方法。
併發程式設計中的一些設計模型也是指導如何分工:生產者——消費者Thread-Per-MessageWork Thread等。

 同步(效能)

在併發程式設計的同步,主要指的就是執行緒間的協作。即當一個執行緒執行完了,該如何通知後續任務的執行緒展開工作。

協作一般和分工相關。Java SDK中ExecutorFork/JoinFuture本質上是分工方法但是也解決執行緒之間的協作問題(如Future非同步呼叫,get())。Java SDK裡提供的CountDownLatchCyclicBarrier

PhaserExchanger也是用於解決執行緒之間的協作問題。

執行緒協作問題都可以被描述為:當某個條件不滿足時,執行緒需要等待,當某個條件滿足使,執行緒需要被喚醒執行。

互斥(正確性/執行緒安全)

互斥指的是:在同一時刻,只允許一個執行緒訪問共享變數。

因為 可見性、有序性和原子性(後面會有文章介紹)問題,多個執行緒訪問同一個共享變數會導致結果的不確定 。
為了解決這三個問題,Java語言引入了記憶體模型,記憶體模型提供了一系列的規則,利用這些規則我們可以避免可見性問題、有序性問題,但是還不能完全解決執行緒安全問題。

解決執行緒安全問題的核心方案還是互斥。

實現互斥的核心技術就是鎖。 Java語言中synchronized

、SDK中的各種Lock都可以解決互斥問題,但是鎖卻會帶來效能問題,於是我們就需要平衡。

主要方案有:分場景優化,優化讀多寫少場景:ReadWriteLockStampledLock以及無鎖結構Java SDK中的原子類;其他方案,原理為不共享變數或者變數只允許讀,Java中提供了Thread LocalFinal關鍵字和Copy-on-write 模式。

小結

在看極客時間專欄《Java併發程式設計實戰》學習攻略時,感觸還是比較深。平時學習知識都是“獨立”的,沒有一種“全域性”觀念,也很少聯絡其他一些理論來側面驗證學習的知識,導致學過後就很容易忘記。看了這篇專欄前言後,總結出:學習知識時,要跳出來看全景,鑽進去看本質。要知道每一種技術背後都應該有理論支援,並且這個理論可能是跨領域的,所以,掌握技術背後的理論十分很重要!
針對Java併發程式設計應該要結合作業系統一起來學習,如後面將要介紹的可見性、有序性和原子性。理解可見性就需要了解CPU和快取的知識;理解原子性就需要理解作業系統的知識;很多無鎖演算法也是和CPU快取有關。要聯絡起CPU、記憶體、I/O之間的關係。

參考:
[1]極客時間專欄王寶令《Java併發程式設計實戰