- 判斷一個程序是不是存在數據競爭
如果一個程序中存在兩個有沖突但不存在happens-before
偏序關系的操作,則稱這個程序是存在數據競爭的。
【案例1】假設r1
和r2
是局部變量,A
和B
是共享變量,則
Thread 1 | Thread 2 |
---|---|
1: r2 = A;
|
3: r1 = B;
|
2: B = 1;
|
4: A = 2;
|
由于
- 操作1和操作4是存在沖突的,且不存在happens-before偏序關系;
- 操作2和操作3是存在沖突的,且不存在happens-before偏序關系;
所以這個程序是存在數據競爭的。
- 定義了一個
happens-before
一致性內存模型
對于被同步的共享變量,保證弱一致性,通過加內存屏障(Memory Barrier)和緩存一致性協議實現,具體地講:
- 讀volatile變量或者進入鎖,都會保證進入一致性;
- 寫volatile變量或者釋放鎖,都會保證釋放一致性;
- 同步變量的操作不允許進行指令重排序,保證了其順序性;
結合Pentium和PowerPC處理器中廣泛使用的一種主流的緩存一致性協議--MESI來解釋下。
假設CPU1執行線程A,CPU2執行線程B;CPU1和CPU2都有高速緩存;線程A和線程B共享volatile變量started,
- 當CPU1從地址started=false讀取數據,會廣播它的讀請求,如果沒有收到響應,即表明CPU1是最先開始讀取started的,然后將數據存入它的緩存并置為exclusive;
- 當CPU2也從地址started讀取數據時,會廣播它的讀請求:CPU1在收到該讀請求后,檢測到地址沖突,將緩存中的拷貝狀態置為為shared,作為響應;CPU2收到CPU1的響應后,將數據存入它的緩存并置為shared;
- 當CPU2寫started,即started=true時,將緩存中started狀態置為modified,并廣播其寫請求;CPU1在收到請求后,將started的拷貝狀態置為Invalid,作為響應;
- 當CPU1再從started讀取數據時,會廣播它的讀請求;CPU2收到請求后,將started=true的數據發送到CPU1和刷新到主內存,設置started拷貝的狀態為shared,作為響應;CPU1收到CPU2的響應后,將數據存入它的緩存并置為shared;
對于沒有被同步的共享變量,
- 只保證其正確性,即從單線程執行的角度來說,程序的執行結果和程序定義的結果是一致性的;
- 不保證其順序性,因為沒有數據相關性的代碼是可以重排序的;
- 判斷一個程序是不是被正確地同步了
一個程序里的所有具有順序一致性的執行都是無數據競爭的當且僅當這是一個被正確同步的程序。
根據上述定義,顯然有:
- 如果一個程序被正確同步了,則就不需要考慮代碼重排因素對程序的影響了;
- 正確同步一個程序可以用來防止由于代碼重排導致的違反直覺的執行結果,但并不保證一個程序的正確性;
- 如果一個程序被正確同步了,則程序員就可以以一種簡單的方式來考慮程序的執行了;
具體的案例請參考:
https://github.com/liyanghao/java-study/blob/master/src/concurrency/happens-before-examples.md
【參考資料】
- https://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.4.5
- 《聊聊高并發(三十三)Java內存模型那些事(一)從一致性(Consistency)的角度理解Java內存模型》http://blog.csdn.net/iter_zc/article/details/41943387
- 《深入理解Java內存模型(三)——順序一致性》http://www.infoq.com/cn/articles/java-memory-model-3
- 《為什么程序員需要關心順序一致性(Sequential Consistency)而不是Cache一致性(Cache Coherence?)》
http://www.infoq.com/cn/articles/java-memory-model-3