volatile
舉個例子
一個線程讀取數據,一個線程修改數據,存在數據的不一致性
機器硬件CPU與JMM
CPU Cache模型
我們都知道,在CPU和內存之間,有着天壤之別的處理速度.高速的cpu和低速的主存,我們不可能讓高速的CPU一直等着低速的內存完成IO才繼續操作,所以在CPU和主存之間引入了多級緩存來緩解這一問題.目的是提高CPU吞吐量.
當需要數據的時候CPU會首先在Cache L1中取去找,找不到會在L2中找,都找不到纔會在主存中找
CPU緩存的一致性問題
每一個CPU修改內存數據的步驟:
1.從內存中把數據讀到cache中
2.在cache中更新數據
3.把更新的結果刷新到內存
當cpu1把i=3這條數據讀到cache中進行修改時,這時候還沒來得及寫回主存,cpu2又來拿i的值,那麼他拿到的i值有可能是3也有可能是4.
解決方案:
1) 總線加鎖 (粒度太大)
2) MESI
a. 讀操作: 不做任何事情,把cache中的數據讀到寄存器
b. 寫操作: 發出信號通知其他的cpu將變量的cache line置爲無效狀態,其他的cpu要訪問這個變量的時候,只能從內存中獲取.
Cache line: CPU的cache中會增加很多的Cache line,CPU要讀一個變量的時候會先檢測這個變量的Cache line是不是有效的,有效的時候再去讀,無效的時候就要去內存中讀.
Java內存模型
-
主存中的數據所有線程都可以訪問
-
每個線程都有自己的工作空間,(本地內存)
-
工作空間數據:局部變量,內存的副本
-
線程不能直接修改內存中的數據,只能讀到工作空間來修改,修改完成後刷新到內存
volatile語義分析
volatile作用:讓其他線程能夠馬上感知到某一線程對某個變量的修改
保證可見性
對共享變量的修改,其他線程馬上能感知到
當你對一個共享變量加了一個volatile時,某線程要對這個變量進行修改,會向其他線程發出控制信號,控制信號映射到硬件上就是將cache line置爲無效.
那麼這時候其他線程從主存中讀取到的數據一定就是最新的嗎?
不一定,因爲volatile不能保證原子性,比如多個線程對某共享變量進行++操作,但由於++操作本身就不是一個原子操作,所以其他線程讀到內存中的數據可能是還沒被寫回來的數據,所以造成了數據的不一致.
保證有序性
重排序:輸入程序的代碼順序並不是實際執行的順序.jvm會在在編譯階段和指令優化階段進行代碼順序的調整,爲了提高效率.
重排序對單線程沒有影響,對多線程有影響.
對於volatile修飾的變量:
1) volatile之前的代碼不能調整到他的後面
2) volatile之後的代碼不能調整到他的前面
volatile的原理和實現機制
volatile可見性的實現就是藉助了CPU的lock指令,通過在寫volatile的機器指令前加上lock前綴,使寫volatile具有以下兩個原則:
-
寫volatile時處理器會將緩存寫回到主內存。
-
一個處理器的緩存寫回到內存會導致其他處理器的緩存失效。
volatile有序性的保證就是通過禁止指令重排序來實現的。指令重排序包括編譯器和處理器重排序,JMM會分別限制這兩種指令重排序。
- 在每個volatile寫操作的前面插入一個StoreStore屏障,防止寫volatile與後面的寫操作重排序。
- 在每個volatile寫操作的後面插入一個StoreLoad屏障,防止寫volatile與後面的讀操作重排序。
- 在每個volatile讀操作的後面插入一個LoadLoad屏障,防止讀volatile與後面的讀操作重排序。
- 在每個volatile讀操作的後面插入一個LoadStore屏障,防止讀volatile與後面的寫操作重排序。
volatile使用場景
狀態標誌
package sync;
public class ShutDownDemo extends Thread{
private volatile boolean started = true;
@Override
public void run() {
while (started){
dowork();
}
}
public void shutDown() {
started = false;
}
}
雙重檢查鎖定
單例模式的應用
package sync;
public class Singleton {
private volatile static Singleton singleton;
private Singleton(){}
public static Singleton getInstance(){
if (singleton == null){
synchronized (Singleton.class){
if (singleton == null){
singleton = new Singleton();
}
}
}
return singleton;
}
}
需要利用順序性
volatile與synchronized的區別
使用上的區別
volatile只能修飾變量,synchronized可以修飾方法和語句塊
對原子性的保證
synchronized可以保證原子性,volatile不能保證原子性
對可見性的保證
都可以保證可見性,但實現原理不同
volatile對變量加了lock
synchronized使用monitorenter和monitorexit
對有序性的保證
volatile能夠保證有序性,synchronized也可以保證有序性,但是代價(重量級)太大,由併發退化到串行
其他
synchronized會引起阻塞
volatile不會引起阻塞