一、lock(鎖定):做用於主內存的變量,把一個變量標記爲一條線程獨佔狀態java
二、unlock(解鎖):做用於主內存的變量,把一個處於鎖定狀態的變量釋放出來,釋放後的變量才能夠被其餘線程鎖定緩存
三、read(讀取):做用於主內存的變量,把一個變量值從主內存傳輸到線程的工做內存中,以便隨後的load動做使用安全
四、load(載入):做用於工做內存的變量,它把read操做從主內存中獲得的變量值放入工做內存的變量副本中多線程
五、use(使用):做用於工做內存的變量,把工做內存中的一個變量值傳遞給執行引擎併發
六、assign(賦值):做用於工做內存的變量,它把一個從執行引擎接收到的值賦給工做內存的變量高併發
七、store(存儲):做用於工做內存的變量,把工做內存中的一個變量的值傳送到主內存中,以便隨後的write的操做性能
八、write(寫入):做用於工做內存的變量,它把store操做從工做內存中的一個變量的值傳送到主內存的變量中測試
整個執行流程如圖優化
read ---load store----writr必須成對執行this
經過上面分析咱們能夠看出即便在java裏面執行i++這樣的操做,對於咱們的底層來講也不是原子操做,由於i++,也須要將這八大操做走一遍,具體來講,read ---load 將主內存中i=0在工做內存中也copy一份,
線程讀到工做內存中的i=0並加1操做即結果i=1寫回工做內存(use---assign),而後將i=1寫回主內存(store----writrt)這一步若是沒有用緩存一致性協議,會有延時不會當即寫到主內存,參考第一篇緩存一執行性協議講解。
volatile是Java虛擬機提供的輕量級的同步機制
volatile語義有以下兩個做用
可見性:保證被volatile修飾的共享變量對全部線程總數可見的,也就是當一個線程修改了一個被volatile修飾共享變量的值,新值老是能夠被其餘線程當即得知。
有序性:禁止指令重排序優化。
volatile緩存可見性實現原理
JMM內存交互層面:volatile修飾的變量的read、load、use操做和assign、store、write必須是連續的,即修改後必須當即同步會主內存,使用時必須從主內存刷新,由此保證volatile變量的可見性。 底層實現:經過彙編lock前綴指令,它會鎖定變量緩存行區域並寫回主內存,這個操做稱爲「緩存鎖定」,緩存一致性機制會阻止同時修改被兩個以上處理器緩存的內存區域數據。一個處理器的緩存回寫到內存內存會致使其餘處理器的緩存無效
先上一段代碼:
public class VolatileVisibilitySample { private boolean initFlag = false; static Object object = new Object(); public void refresh(){ this.initFlag = true; //普通寫操做,(volatile寫) String threadname = Thread.currentThread().getName(); System.out.println("線程:"+threadname+":修改共享變量initFlag"); } public void load(){ String threadname = Thread.currentThread().getName(); int i = 0; while (!initFlag){ } System.out.println("線程:"+threadname+"當前線程嗅探到initFlag的狀態的改變"+i); } public static void main(String[] args){ VolatileVisibilitySample sample = new VolatileVisibilitySample(); Thread threadA = new Thread(()->{ sample.refresh(); },"threadA"); Thread threadB = new Thread(()->{ sample.load(); },"threadB"); threadB.start(); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } threadA.start(); } }
代碼很好理解,線程B讀取成員變量initFlag 若是爲false無線循環,若是爲true,打出表示語,線程A負責將initFlag改成true,線程B先啓動,線程A啓動修改標誌爲true後,看看線程B可否感知到並終止循環
測試結果 :線程B無線循環,未能感知到標誌被線程A修改,緣由,線程B一直讀的是工做空間的緩存數據,當線程A修改數據以後,線程B未能感知到.
降上訴代碼修改,線程B的執行任務上加鎖synchronized:
public void load(){ String threadname = Thread.currentThread().getName(); int i = 0; while (!initFlag){ synchronized (object){ i++; } } System.out.println("線程:"+threadname+"當前線程嗅探到initFlag的狀態的改變"+i); }
測試結果:加鎖會致使線程B失去cpu執行權,當再次獲取cpu執行權時,會引發線程上下文切換,這個過程會引發從新讀取主內存數據。
volatile關鍵字測試
initFlag用volatile修飾後
private volatile boolean initFlag = false;
測試結果:當線程A修改initFlag後線程B能當即感知到,中止循環打出標誌語;
緣由:線程A修改initFlag,因爲initFlag被volatile修飾,會當即從工做內存刷到主內存,同時讓其餘線程中工做內存中initFlag數據緩存失效,這樣線程B中原來地緩存失效,從主內存中從新讀取新值。
先來一段代碼:
public class VolatileAtomicSample { private static volatile int counter = 0; public static void main(String[] args) { for (int i = 0; i < 10; i++) { Thread thread = new Thread(()->{ for (int j = 0; j < 1000; j++) { counter++; //不是一個原子操做,第一輪循環結果是沒有刷入主存,這一輪循環已經無效 //1 load counter 到工做內存 //2 add counter 執行自加 //其餘的代碼段? } }); thread.start(); } try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(counter); } }
開10個線程每一個線程對counter進行1000次+最總咱們地結果也不是10000,而是小於10000,
緣由:couter++並非原子操做,好比兩個線程讀到counter=0都讀到本身地工做內存,而後加1以後都要往咱們地主內存寫,這時候必然引發裁決,致使一個線程的+1有效果,一個線程的+1無效果,最後致使
兩個線程一共加了兩次1,只有一個有效,最後結果比預期結果小。
在Java裏面,能夠經過volatile關鍵字來保證必定的「有序性」(具體原理在下一節講述volatile關鍵字)。另外能夠經過synchronized和Lock來保證有序性,很顯然,synchronized和Lock保證每一個時刻是有一個線程執行同步代碼,至關因而讓線程順序執行同步代碼,天然就保證了有序性。
指令從排序發生在編譯重排序和處理器重排序,禁止指令重排序的底層就是內存屏障,內存屏障分爲4種
一、StoreStore 二、StoreLoad 三、LoadLoad 四、LoadStore
public void run() { //因爲線程one先啓動,下面這句話讓它等一等線程two. 讀着可根據本身電腦的實際性能適當調整等待時間. shortWait(10000); a = 1; //是讀仍是寫?store,volatile寫 //storeload ,讀寫屏障,不容許volatile寫與第二部volatile讀發生重排 //手動加內存屏障 UnsafeInstance.reflectGetUnsafe().storeFence(); x = b; // 讀仍是寫?讀寫都有,先讀volatile,寫普通變量 //分兩步進行,第一步先volatile讀,第二步再普通寫 } });
大量使用volatile會引發工做緩存有大量的無效緩存,並且volatile會一塊兒會引發線程之間相互監聽,嗅探,這些都會佔用總線資源,致使總線資源負載太高。這時候咱們須要鎖來解決問題,這就是爲何有了
volatile咱們還須要synchronized,lock鎖,由於volatile保證不了原子操做,且用的過多會致使總線風暴。
public class Singleton { /** * 查看彙編指令 * -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -Xcomp */ private volatile static Singleton myinstance; public static Singleton getInstance() { if (myinstance == null) { synchronized (Singleton.class) { if (myinstance == null) { myinstance = new Singleton();//對象建立過程,本質能夠分文三步 //對象延遲初始化 // } } } return myinstance; } public static void main(String[] args) { Singleton.getInstance(); } }
解釋:建立對象myinstance = new Singleton() 並不時一個原子操做,它能夠分爲三部,一、申請空間,2,實力化對象,3,地址賦值給myinstance 變量,加synchronized 保證了原子操做,可是沒法防止指令重排,線程1申請完空間以後若是發生指令重排直接執行第3步賦值,那麼線程2執行if判斷時myinstance 不爲空可是卻沒有實例化對象。這是指令重排致使的,因此volatile 修飾myinstance防止發生指令重排。----超高併發下的應用。