理解併發程式設計的幾種"性" -- 可見性,有序性,原子性

zhoubangding發表於2017-02-16

轉自:http://blog.sina.com.cn/s/blog_4adc4b090102whzx.html

這篇的主題本應該放在最初的幾篇,討論的是併發程式設計最基礎的幾個核心概念,但是這幾個概念又牽扯到很多的實際技術,比如Java記憶體模型,各種鎖的實現,volatile的實現,原子變數等等,每一個都可以展開寫很多,尤其是Java記憶體模型,網上已經能夠有很幾篇不錯的文章,暫時不想重複造輪子,這裡推薦幾篇Jave記憶體模型的資料:

1. JSR-133 FAQ

2. JSR-133 Cookbook

3. Synchronization and Java Memory Model

4. 深入理解Java記憶體模型


我之前也寫了一個Java記憶體模型的PPT: http://share.csdn.net/slides/7916


下面說說併發程式設計關注的幾個核心概念。關注一個併發問題,有3個基本的關注點

1. 安全性,也就是正確性,指的是程式在併發情況下執行的結果和預期一致

2. 活躍性,比如死鎖,活鎖

3. 效能,減少上下文切換,減少核心呼叫,減少一致性流量等等


安全性問題是首要解決的問題,保證程式的執行緒安全,實際上就是對多執行緒的同步,而多執行緒的同步本質上就是多執行緒通訊的問題。作業系統裡面定義了幾種程式通訊的方式:

1. 管道 pipeline

2. 訊號 signal

3. 訊息佇列 messsage queue

4. 共享記憶體 shared memory

5. 訊號量 semaphore

6. Socket


Java裡面進行多執行緒通訊的主要方式就是共享記憶體的方式,共享記憶體主要的關注點有兩個:可見性和有序性。加上覆合操作的原子性,我們可以認為Java的執行緒安全性問題主要關注點有3個

1. 可見性

2. 有序性

3. 原子性


Java記憶體模型JMM解決了可見性和有序性的問題,而鎖解決了原子性的問題。

至於Java記憶體模型如何解決可見性和有序性的問題,以後會說到,感興趣的同學可以看看上面的資料。


 

可見性指的是一個執行緒對變數的寫操作對其他執行緒後續的讀操作可見。由於現代CPU都有多級快取,CPU的操作都是基於快取記憶體的,而執行緒通訊是基於記憶體的,這中間有一個Gap,可見性的關鍵還是在對變數的寫操作之後能夠在某個時間點顯示地寫回到主記憶體,這樣其他執行緒就能從主記憶體中看到最新的寫的值。volatile,synchronized(隱式鎖),顯式鎖,原子變數這些同步手段都可以保證可見性。
可見性底層的實現是通過加記憶體屏障實現的:
1. 寫變數後加寫屏障,保證CPU寫緩衝區的值強制重新整理回主記憶體
2. 讀變數之前加讀屏障,使快取失效,從而強制從主記憶體讀取變數最新值
寫volatile變數 =進入鎖
讀volatile變數 =釋放鎖



有序性指的是資料不相關的變數在併發的情況下,實際執行的結果和單執行緒的執行結果是一樣的,不會因為重排序的問題導致結果不可預知。volatile,final, synchronized,顯式鎖都可以保證有序性
有序性的語意有幾層,
1. 
最常見的就是保證多執行緒執行的序列順序
2. 防止重排序引起的問題
3.程式執行的先後順序,比如JMM定義的一些Happens-before規則
 


重排序的問題是一個單獨的主題,常見的重排序有3個層面:
1. 編譯級別的重排序,比如編譯器的優化
2. 指令級重排序,比如CPU指令執行的重排序
3. 記憶體系統的重排序,比如快取和讀寫緩衝區導致的重排序


原子性是指某個(些)操作在語意上是原子的。比如讀操作,寫操作,CAS(compare andset)操作在機器指令級別是原子的,又比如一些複合操作在語義上也是原子的,如先檢查後操作if(xxx== null){}
有個專有名詞競態條件來描述原子性的問題。
競態條件(racingcondition)是指某個操作由於不同的執行時序而出現不同的結果,比如先檢查後操作。
volatile變數只保證了可見性,不保證原子性, 比如a++這種操作在編譯後實際是多條語句,比如先讀a的值,再加1操作,再寫操作,執行了3個原子操作,如果併發情況下,另外一個執行緒很有可能讀到了中間狀態,從而導致程式語意上的不正確。所以a++實際是一個複合操作
加鎖可以保證複合語句的原子性,sychronized可以保證多條語句在synchronized塊中語意上是原子的。
顯式鎖保證臨界區的原子性。
原子變數也封裝了對變數的原子操作
非阻塞容器也提供了原子操作的介面,比如putIfAbsent。

 

理解可見性,有序性,原子性是理解併發程式設計的一個重要基礎


相關文章