【Java並發基礎】並發編程領域的三個問題:分工、協作和同步


前言

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

分工(性能)

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

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

協作(性能)

在並發編程的協作指的是當一個線程執行完了,該如何通知后續任務的線程展開工作。

協作一般和分工相關。Java SDK中ExecutorFork/JoinFuture本質上是分工方法但是也解決線程之間的協作問題(如Future異步調用,get())。Java SDK里提供的CountDownLatchCyclicBarrierPhaserExchanger也是用於解決線程之間的協作問題。

深入了解下,線程間協作所使用的通信機制。在命令式編程中,線程之間的通信機制有兩種:共享內存和消息傳遞。
在共享內存的並發模式里,線程之間共享程序的公共狀態,線程之間通過寫-讀內存中的公共狀態來隱式進行通信。
在消息傳遞的並發模型里,線程之間沒有公共狀態,線程之間必須通過明確的發送消息來顯式進行通信。

Java的並發采取的就是共享內存模型,Java線程之間的通信總是隱式地進行,所以只有理解了這種通信模型,當遇見關於內存可見性問題時才理解如何解決。

同步(正確性/線程安全)

當多個線程訪問某個共享變量並且其中有一個線程執行寫操作時,就必須要采用同步機制來協同這些線程對共享變量的訪問。或者說是控制不同線程之間操作發生相對順序的機制。 在共享內存並發模型里,同步是顯式進行的,我們必須顯式指定某個方法或者某段代碼塊需要在線程之間互斥執行。在消息傳遞的並發模型里,由於消息的發送必須在消息的接收之前,因此同步是隱式進行的。

因為 可見性有序性原子性(后面會有文章介紹)問題,多個線程對一個共享變量進行操作會導致結果的不確定 。
為了解決這三個問題,Java語言引入了內存模型,內存模型提供了一系列的規則,利用這些規則我們可以避免可見性問題、有序性問題,但是還不能完全解決線程安全問題。

解決線程安全問題的核心方案還是同步機制

同步機制的核心技術就是鎖,當然還有非鎖方案,如volatile類型的變量和原子變量。 Java語言中synchronized、SDK中的各種Lock都可以解決同步問題,但是鎖卻會帶來性能問題,於是我們就需要平衡。

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

小結

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

參考:
[1]極客時間專欄王寶令《Java並發編程實戰》
[2]Brian Goetz.Tim Peierls. et al.Java並發編程實戰[M].北京:機械工業出版社,2016


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM