7.volatile怎么通过内存屏障保证可见性和有序性?
volatile通過內存屏障保證可見性
小陳:老王,你上一篇拋出一個問題volatile怎么通過內存屏障保證可見性和有序性?我現在迫不及待的想知道了。
老王:嗯嗯,我們慢慢來講,先說說volatile怎么通過內存屏障來保證可見性?
小陳:volatile關鍵字實際上是怎么使用內存屏障的呢?
老王:是這樣子的。
volatile修飾的變量,在每個讀操作(load操作)之前都加上Load屏障,強制從主內存讀取最新的數據。每次在assign賦值后面,加上Store屏障,強制將數據刷新到主內存。
老王:以volatile int = 0;線程A、B進行 i++?的操作來畫圖給你講解一下:
如上圖所示:
(1)線程A讀取 i 的值遇到Load屏障,需要強制從主存讀取得到 i = 0; 然后傳遞給工作線程執行++操作
(2)cpu執行 i++ 操作得到 i = 1,執行assign指令進行賦值;然后遇到Store屏障,需要強制刷新回主內存,此時得到主內存 i = 1
(3)然后線程B執行讀取 i 遇到Load屏障,強制從主內存讀取,得到最新的值 i = 1,然后傳給工作線程執行++操作,得到 i = 2,同樣在賦值后遇到Store屏障立即將數據刷新回主內存
老王:通過上面的圖和講解,以及volatile讀取前加的Load屏障、賦值后加的Store屏障看懂了嗎?
小陳:哦哦,通過這樣說我就明白了。
其實說白了就是通過一個屏障讓volatile的變量每次讀都讀主存,每次修改后立即刷到主存里面。
好比線程A修改 i 后立即將值刷到主存里面,后面線程B用到的時候強制從主存讀取,這個時候它能看到的值是線程A修改之后的值了。也就是通過這種方式來保證多線程之間的可見性吧。
老王:嘿嘿,沒錯;就是這個意思......
小陳:volatile通過內存屏障每次走主存的方式;這樣來保障可見性,我理解了,害~,感覺也不難嘛......
老王:哈哈,這個本來就不難,只是你需要先了解一下內存屏障,以及這些屏障的作用是什么。再加上之前講過多核CPU高速緩存、JAVA內存模型,你理解起來就很容易了。如果沒有之前的知識做鋪墊,你理解起來就費勁了......
小陳:哈哈,好像也是啊......
老王:好了,下面我們繼續來討論一下volatile怎么通過內存屏障來保證有序性?
volatile通過內存屏障保證有序性
老王:小陳啊,之前講過一個有序性問題導致異常的例子,你還記得不?
小陳:記得啊,我記得當時是這樣說的:
線程A的執行代碼:
// 步驟1 dataSource = initDataSource(); // 步驟2 httpClient = initHttpClient(); // 步驟3 initOK = true;線程B的執行代碼:
// 步驟4 while(!initOK) { } // 步驟5 Object data = dataSource.getData(); // 步驟6 httpClient.request(data);由于線程A先執行了initOK = true。導致線程B提前跳出了while循環!!!,然后線程B調用dataSource.getData的時候發現dataSource沒初始化好,竟然是個坑爹的null,導致代碼報錯了。
老王:哈哈,看來你很用功啊;之前的例子你都記得。
老王:現在我們就來講講將initOk用volatile來修飾,是可以做到線程A有序性執行的。
好了,廢話不多說,我先來上代碼:
// 步驟1 dataSource = initDataSource(); // 步驟2 httpClient = initHttpClient(); // 步驟3 initOK = true;對應到指令可能是這樣的:
// 步驟1 對應上面dataSource = initDataSource(); store datasource指令 // 步驟2 對應上面httpClient = initHttpClient(); store http指令StoreStore屏障 (注意:在store initOK前面加了一個StoreStore屏障) // 步驟3 對應上面initOK = true; store initOk = true指令 StoreLoad 屏障 (注意:在store initOK后面加了一個StoreLoad屏障)注意這里:store initOk指令的前面加了一道StoreStore屏障;后面加了一道StoreLoad屏障
所以通過volatile修飾initOK,加了屏障之后;store initOK = true 這一條指令是不能跳到store dataSource、store http前面去的,所以必須先執行完前面的執行之后,才能執行store initOK = true
這樣對于線程B來說,加了內存屏障之后,它看到線程A就是資源初始化完成之后,才將initOK表示設置為true的,這樣它看到線程A的執行就是有序的
老王:小陳,我這么說你懂了不?
小陳:稍等,我來捋捋思路....
也就是通過加了屏障,store initOK = true 指令不能跟前面的store指令進行交換。所以它就自然得等前面的store指令執行完了之后,才執行store initOK = true的對吧? 然后在線程B那一側看到的initOK = true的時候,發現資源以及初始化好了,自然就不會報錯了。
老王:bingo,就是這個道理....
小陳:這個volatile寫的時候前面加StoreStore屏障、寫的后面加StoreLoad屏障來禁止重排序的我看懂了。當volatile讀的時候加什么屏障來禁止重排序?
老王:這個就當作思考題,你自己再去看看咯,原理也是一樣的......
小陳:好的老王,在線程安全來說volatile保證了可見性、有序性了;我看過一些資料說volatile是不能保證原子性的,那它為啥不能保證原子性啊?
老王:今天我們先講到這里,你先消化消化。我們下一章再來討論volatile不能保證原子性的問題......
關注小陳,公眾號上更多更全的文章
JAVA并發文章目錄(公眾號)
JAVA并發專題 《筑基篇》
1.什么是CPU多級緩存模型?
2.什么是JAVA內存模型?
3.線程安全之可見性、有序性、原子性是什么?
4.什么是MESI緩存一致性協議?怎么解決并發的可見性問題?
JAVA并發專題《練氣篇》
5.volatile怎么保證可見性?
6.什么是內存屏障?具有什么作用?
7.volatile怎么通過內存屏障保證可見性和有序性?
8.volatile為啥不能保證原子性?
9.synchronized是個啥東西?應該怎么使用?
10.synchronized底層之monitor、對象頭、Mark Word?
11.synchronized底層是怎么通過monitor進行加鎖的?
12.synchronized的鎖重入、鎖消除、鎖升級原理?無鎖、偏向鎖、輕量級鎖、自旋、重量級鎖
13.synchronized怎么保證可見性、有序性、原子性?
JAVA并發專題《結丹篇》
14. JDK底層Unsafe類是個啥東西?
15.unsafe類的CAS是怎么保證原子性的?
16.Atomic原子類體系講解
17.AtomicInteger、AtomicBoolean的底層原理
18.AtomicReference、AtomicStampReference底層原理
19.Atomic中的LongAdder底層原理之分段鎖機制
20.Atmoic系列Strimped64分段鎖底層實現源碼剖析
JAVA并發專題《金丹篇》
21.AQS是個啥?為啥說它是JAVA并發工具基礎框架?
22.基于AQS的互斥鎖底層源碼深度剖析
23.基于AQS的共享鎖底層源碼深度剖析
24.ReentrantLock是怎么基于AQS實現獨占鎖的?
25.ReentrantLock的Condition機制底層源碼剖析
26.CountDownLatch 門栓底層源碼和實現機制深度剖析
27.CyclicBarrier 柵欄底層源碼和實現機制深度剖析
28.Semaphore 信號量底層源碼和實現機深度剖析
29.ReentrantReadWriteLock 讀寫鎖怎么表示?
30. ReentrantReadWriteLock 讀寫鎖底層源碼和機制深度剖析
JAVA并發專題《元神篇》并發數據結構篇
31.CopyOnAarrayList 底層分析,怎么通過寫時復制副本,提升并發性能?
32.ConcurrentLinkedQueue 底層分析,CAS 無鎖化操作提升并發性能?
33.ConcurrentHashMap詳解,底層怎么通過分段鎖提升并發性能?
34.LinkedBlockedQueue 阻塞隊列怎么通過ReentrantLock和Condition實現?
35.ArrayBlockedQueued 阻塞隊列實現思路竟然和LinkedBlockedQueue一樣?
36.DelayQueue 底層源碼剖析,延時隊列怎么實現?
37.SynchronousQueue底層原理解析
JAVA并發專題《飛升篇》線程池底層深度剖析
38. 什么是線程池?看看JDK提供了哪些默認的線程池?底層竟然都是基于ThreadPoolExecutor的?
39.ThreadPoolExecutor 構造函數有哪些參數?這些參數分別表示什么意思?
40.內部有哪些變量,怎么表示線程池狀態和線程數,看看道格.李大神是怎么設計的?
41. ThreadPoolExecutor execute執行流程?怎么進行任務提交的?addWorker方法干了啥?什么是workder?
42. ThreadPoolExecutor execute執行流程?何時將任務提交到阻塞隊列? 阻塞隊列滿會發生什么?
43. ThreadPoolExecutor 中的Worker是如何執行提交到線程池的任務的?多余Worker怎么在超出空閑時間后被干掉的?
44. ThreadPoolExecutor shutdown、shutdownNow內部核心流程
45. 再回頭看看為啥不推薦Executors提供幾種線程池?
46. ThreadPoolExecutor線程池篇總結
總結
以上是生活随笔為你收集整理的7.volatile怎么通过内存屏障保证可见性和有序性?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: OpenGL学习小结
- 下一篇: Java学习day05——方法及其调用重