多線程下變量的不可見性
- 多線程併發執行下,多個線程修改共享的成員變量,會出現一個線程修改了共享變量後,另一個線程不能立即獲取修改後的最新值。
- 解決方案:
- 加鎖:使用synchronized關鍵字或者使用JDK的Lock
- 使用volatile關鍵字
- volatile解決共享變量不可見問題的過程:
- 線程2將共享變量從主內存讀取到數據放到其對應的工作內存
- 線程2在工作內存中更改了共享變量副本的值,使用volatile關鍵字會強制將修改的值立即寫入主存
- 使用volatile關鍵字的話,當線程2將共享變量從工作內存中寫回主內存後,其他線程工作內存中的共享變量副本無效(CPU主線嗅探機制)(反映到硬件層的話,就是CPU的L1或者L2緩存中對應的緩存行無效)
- 線程1再次讀取共享變量時需要從主內存中讀取最新的值放到自己的工作內存中
volatile特性
- volatile可以保證線程可見性且提供了一定的有序性,但是無法保證原子性。在JVM底層volatile是採用“內存屏障”來實現的。
- 可以保證可見性
- 不保證原子性
- 如何保證volatile變量的原子性:
- 使用加鎖機制
- 使用Atomic類
- 禁止指令重排序,保證有序性
- volatile可以禁止指令重排序,從而修正重排序可能帶來的併發安全問題
- volatile關鍵字禁止指令重排序有兩層意思:
1)當程序執行到volatile變量的讀操作或者寫操作時,在其前面的操作的更改肯定全部已經進行,且結果已經對後面的操作可見;在其後面的操作肯定還沒有進行;
2)在進行指令優化時,不能將在對volatile變量訪問的語句放在其後面執行,也不能把volatile變量後面的語句放到其前面執行。
重排序導致的問題
- 指令重排序導致多個線程操作之間的不可見性
happens-before
happens-before:前一個操作的結果可以被後續的操作獲取
happens-before 規則
- 程序順序規則:一個線程中的每個操作,happens-before於該線程中的任意後續操作。
- 監視器鎖規則:對一個鎖的解鎖,happens-before於隨後對這個鎖的加鎖。
- volatile變量規則:對一個volatile域的寫,happens-before於任意後續對這個volatile域的讀。
- 線程1寫入了volatile變量,然後線程2讀取該變量,那麼線程1寫入volatile遍歷之前的寫操作對線程2可見(線程1和線程2可以是同一個線程)
- 傳遞性:如果A happens-before B,且B happens-before C,那麼A happens-before C。
- start()規則:如果線程A執行操作ThreadB.start()(啓動線程B),那麼A線程的ThreadB.start()操作happens-before於線程B中的任意操作。
- join()規則:如果線程A執行操作ThreadB.join()併成功返回,那麼線程B中的任意操作happens-before於線程A從ThreadB.join()操作成功返回。
- 程序中斷規則:對線程interrupted()方法的調用先行於被中斷線程的代碼檢測到中斷時間的發生。
- 對象finalize規則:一個對象的初始化完成(構造函數執行結束)先行於發生它的finalize()方法的開始。
volatile 重排序規則(volatile可以禁止指令重排序)
- 寫volatile變量是,無論前一個操作是什麼,都不能重排序
- 讀volatile變量時,無論後一個操作是什麼,都不能重排序
- 先寫volatile變量,後讀volatile變量是,不能重排序
volatile原理和實現機制——內存屏障
“觀察加入volatile關鍵字和沒有加入volatile關鍵字時所生成的彙編代碼發現,加入volatile關鍵字時,會多出一個lock前綴指令”
lock前綴指令實際上相當於一個內存屏障(也成內存柵欄),內存屏障會提供3個功能:
1)它確保指令重排序時不會把其後面的指令排到內存屏障之前的位置,也不會把前面的指令排到內存屏障的後面;即在執行到內存屏障這句指令時,在它前面的操作已經全部完成;
2)它會強制將對緩存的修改操作立即寫入主存;
3)如果是寫操作,它會導致其他CPU中對應的緩存行無效。
volatile與synchronized的區別
- volatile只能修飾實例變量和類變量,synchronized修飾的是代碼塊、方法
- volatile保證數據的可見性,但是不保證原子性(多線程進行寫操作,不保證線程安全),而synchronized是一種排他(互斥)的機制,集保證原子性也保證可見性
- volatile用於禁止指令重排序:可以解決單例雙重檢查對象初始化代碼執行亂序問題
- volatile可以看作是輕量版synchronized,volatile不保證原子性,但是如果對一個共享變量進行賦值,而沒有其他操作,那麼可以用volatile代替synchronized,因爲賦值本身就是原子性的,而volatile又保證了可見性,所以可以保證線程安全。