volatile
解釋
volatile修飾的變量能夠保證可見性,但不保證原子性,每個線程能夠獲取該變量的最新值。
如何保持可見性
對volatile修飾的詞,程序在編譯的時候,會多一個lock彙編指令如下圖所示:
該lock指令有兩個主要作用:
- 將當前緩存行的數據回寫到內存中
- 使其他cpu裏緩存了該內存地址的數據無效(緩存一致性機制)
JMM主要是通過設置內存屏障來禁止指令重排序,下圖是彙編程序中的四種內存屏障類型
屏障類型 | 說明 |
---|---|
LoadLoad Barriers | 保證Load1數據的裝載先於Load2及所有後續指令的裝載 |
StoreStore Barriers | 保證Store1數據對其他處理器的可見先於Store2及所有後續存儲指令的存儲 |
LoadStore Barriers | 確保Load1數據裝載先於Store2及所有後續的存儲指令刷新到內存 |
StoreLoad Barriers | 確保Store1數據對其他處理器的可見先於Load2及所有後續裝載指令的裝載 |
volatile是如何防止指令重排序的:
- volatile寫操作前面插入一個StoreStore屏障
- volatile寫操作後面插入一個StoreLoad屏障
- volatile讀操作後面插入一個LoadLoad屏障
- volatile讀操作後面插入一個LoadStore屏障
適用場景
在使用volatile時需滿足一下兩個條件:
- 對變量的寫操作不依賴於當前值
- 該變量沒有包含在具有其他變量的不變式中
- 不能保持原子性
volatile和synchronized
- 使用區別:volatile只能修飾變量,synchronized只能修飾方法和語句塊
- 原子性:synchronized可以保證原子性,volatile不能保證原子性
- 可見性:都可以保證可見性,但實現原理不同volatile對變量加了lock,synchronized使用monitorEnter和monitorexit monitor JVM
- 有序性:volatile能保證有序,synchronized可以保證有序性,但是代價(重量級)併發退化到串行
- 線程阻塞:synchronized會引起線程阻塞,volatitle不會引起線程阻塞
CPU高速緩存
CPU緩存是位於CPU與內存之間的臨時存儲器,它的容量比內存小的多但是交換速度卻比內存要快得多。高速緩存的出現主要是爲了解決CPU運算速度與內存讀寫速度不匹配的矛盾,因爲CPU運算速度要比內存讀寫速度快很多,這樣會使CPU花費很長時間等待數據到來或把數據寫入內存。在緩存中的數據是內存中的一小部分,但這一小部分是短時間內CPU即將訪問的,當CPU調用大量數據時,就可避開內存直接從緩存中調用,從而加快讀取速度。
下圖爲CPU高級緩存層次圖:
在引入CPU告訴緩存後,會伴隨着緩存不一致的情況的出現,針對此問題,在CPU層引入:
- 總線鎖
- 緩存鎖
緩存鎖的核心是MESI(緩存一致性協議)
MESI
mesi表是緩存的四種狀態,分別是:
Happen-before規則
- 程序順序規則,程序代碼順序,書寫在前面的操作先行於書寫在後面的操作,控制流順序不是代碼順序還要考慮分支和循環。
- 管程鎖定規則,一個unlock操作一定發生在同一個鎖的lock操作之後。
- volatitle規則,對volatitle操作一定寫一定先於後面的讀操作
線程啓動規則,start方法先於所有操作 - 線程終止規則,所有操作都先於線程終止檢查
- 線程中斷規則,對線程interrupt方法調用先於線程終止檢查到中斷事件發生。
- 對象終結規則,一個對象的初始化完成,先行發生於它的finalize的方法開始。
- 傳遞性
- synchronized監控器鎖