當變量被聲明爲 volatile的時候,在對volatile變量進行寫操做時候,彙編指令會插入一個 Lock前綴指令,這個指令會引起兩件事情。java
緩存一致性協議緩存
在多處理器下,爲了保證各個處理器緩存是一致的,就會實現緩存一致性協議,每一個處理器經過嗅探在總線上傳播的數據來檢查本身緩存的值是否是過時了。當處理器發現本身緩存行對應的內存地址被修改,就會將當前處理器的緩存行置爲無效狀態。app
從 jdk5 開始,volatile變量的寫-讀能夠實現線程之間的通訊。
從內存語義的角度來看,volatile寫-讀與鎖的釋放-獲取具備相同的內存效果。volatile寫和鎖的釋放具備相同的內存語義,volatile讀與鎖的獲取有相同的內存語義。線程
爲了實現volatile內存語義,JMM將限制編譯器重排序和處理器重排序。JMM針對編譯器制定的volatile重排序規則表。code
可否重排序 | 第二個操做 | ||
---|---|---|---|
第一個操做 | 普通讀寫 | volatile讀 | volatile寫 |
普通讀寫 | NO | ||
volatile讀 | NO | NO | NO |
volatile寫 | NO | NO |
總結爲一下3點:對象
爲了實現volatile的內存語義,編譯器在生成字節碼的時候,會在指令序列中插入內存屏障來禁止特定類型的處理器重排序。排序
StoreLoad屏障內存
舊的java內存模型中,雖然不容許volatile變量之間重排序,可是容許 volatile變量與普通變量重排序。
所以在舊的內存模型中,volatile的寫-讀沒有鎖的釋放-獲取所具備的語義。爲了實現一種比鎖更輕量級的線程之間的通訊機制。JSR-133中加強了volatile的內存語義,嚴格限制編譯器和處理器對volatile變量與普通變量的重排序。get
public class InstanceFactory { private static Instance instance; public static Instance getInstance(){ if(instance==null){ synchronized(InstanceFactory.class){ if(instance==null){ instance =new Instance(); } } } return instance; } static class Instance{ //some code } }
這個代碼在 看上去是正確,有可能會獲得一個爲徹底初始化成功的對象。編譯器
建立對象的代碼能夠分爲3個部分:
memory = allocate(); //1.分配內存 ctorInstance(memory);//2.初始化對象 instance = memory; //3.設置instance的內存地址
這個過程能夠重排序爲:
memory = allocate(); //1.分配內存 instance = memory; //3.設置instance的內存地址 ctorInstance(memory);//2.初始化對象
因此當第二個判斷 instance == null ,當判斷對象不爲空的時候,對對象進行訪問可能會訪問到一個爲徹底初始化成功的對象。
解決方法:1.禁止 操做二、3重排序
可使用volatile變量修飾instance 禁止操做二、3重排序。 這個方案須要在jdk1.5或者更高的版本才行
public class InstanceFactory { private static volatile Instance instance; public static Instance getInstance(){ if(instance==null){ synchronized(InstanceFactory.class){ if(instance==null){ instance =new Instance(); } } } return instance; } static class Instance{ //some code } }