咱們以前講解了JMM模型,以及其引入的必要行,以及JMM與JVM內存模型的比較和JMM與硬件內存結構的對應關係。segmentfault
本節主要講解思惟導圖以下: 緩存
一、lock(鎖定):做用於主內存的變量,它把一個變量標識爲一條線程獨佔的狀態。
二、unlock(解鎖):做用於主內存的變量,它把一個處於鎖定狀態的變量釋放出來,釋放後的變量 才能夠被其餘線程鎖定。
三、read(讀取):做用於主內存的變量,它把一個變量的值從主內存傳輸到線程的工做內存中,以 便隨後的load動做使用。
四、load(載入):做用於工做內存的變量,它把read操做從主內存中獲得的變量值放入工做內存的 變量副本中。
五、use(使用):做用於工做內存的變量,它把工做內存中一個變量的值傳遞給執行引擎,每當虛 擬機遇到一個須要使用變量的值的字節碼指令時將會執行這個操做。
六、assign(賦值):做用於工做內存的變量,它把一個從執行引擎接收的值賦給工做內存的變量, 每當虛擬機遇到一個給變量賦值的字節碼指令時執行這個操做。
七、store(存儲):做用於工做內存的變量,它把工做內存中一個變量的值傳送到主內存中,以便隨 後的write操做使用。
八、write(寫入):做用於主內存的變量,它把store操做從工做內存中獲得的變量的值放入主內存的變量中。多線程
注意:
一、若是須要把變量總主內存賦值給工做內存:read和load必須是連續;read只是把主內存的變量值從主內存加載到工做內存中,而load是真正把工做內存的值放到工做內存的變量副本中。
二、若是須要把變量從工做內存同步回主內存;就須要執行順序執行store跟write操做。store做用於工做內存,將工做內存變量值加載到主內存中,write是將主內存裏面的值放入主內存的變量中。 優化
代碼實例:this
public class VolatileTest2 { static boolean flag = false; public void refresh(){ this.flag = true; String threadName = Thread.currentThread().getName(); System.out.println("線程: "+threadName+" 修改共享變量flag爲"+flag); } public void load(){ String threadName = Thread.currentThread().getName(); while (!flag){ } System.out.println("線程: "+threadName+" 嗅探到flag狀態的改變"+" flag:"+flag); } public static void main(String[] args) { /** * 建立兩個線程 */ VolatileTest2 obj = new VolatileTest2(); Thread thread1 = new Thread(() -> { obj.refresh(); }, "thread1"); Thread thread2 = new Thread(() -> { obj.load(); }, "thread2"); thread2.start(); try { /** * 確保咱們線程2先執行 */ Thread.sleep(2000); }catch (Exception e){ e.printStackTrace(); } thread1.start(); } }
咱們發現上面代碼數據結果爲:spa
線程: thread1 修改共享變量flag爲true
而且主線程不會退出,說明有用戶線程在runnable運行中,說明線程2一直在運行,也說明線程2獲取的變量值先從主內存read到工做內存,而後load給線程2裏面工做內存裏面變量,而後線程2一直是從本身工做內存獲取數據,而且線程2是while的空轉,搶佔cpu時間多,因此一直不退出。線程
8大原子操做是怎樣作的?變量是如何讀取、如何賦值的?3d
上面是線程2執行後的結果;因此線程2先讀取到flag=false;因此先不會退出。 code
接着線程1會執行修改flag的操做。將flag修改爲true;
第1步:read變量到
第2步: load到工做內存裏去;
第3步: use傳遞給執行引擎作賦值操做。
第4步: 將修改後的值assign到工做內存;這個值會從false變成true;blog
那麼工做內存裏面的新值flag=true會立馬同步到主內存裏面去嗎?
更新後的新值不會立馬同步到咱們的主內存裏面去,他須要等待必定的時機。時機到了以後會同步到咱們的主內存中去;
同步的時候也須要分爲執行兩步驟:store和write操做。
可是更新到主內存爲true以後,爲何咱們的線程2爲何沒有感知到了;緣由線程2在while進行循環判斷的時候,一直判斷的是咱們線程2本身的工做內存裏面的值。執行引擎一直判斷;判斷的值一直是工做內存裏面的值。
而後咱們修改代碼以下;在while循環判斷裏面加一個i++的話,那麼咱們的線程2能不能及時感知到flag變化的值呢?
由於工做內存中已經存在這個值的話,就不會從主內存去加載。
咱們修改代碼以下:線程3去讀取主內存flag的值,由於線程3是從主內存加載的線程1已經寫入的值,此時這個值是flag=true;因此ok。
而後咱們加上一個同步代碼快以後的效果呢?
經過上面分析,咱們的線程2已經感知到了flag數據的變化。 這是什麼緣由呢?這裏不少人都搞不明白,這裏有一個很大的坑:加了同步快以後,咱們的線程2就可以讀取到咱們線程1修改的數據,這個是爲何呢?
緣由:以前咱們說了,以前沒有加同步代碼塊以前,咱們程序指令一直在循環/或者一直在作i++操做。循環是空的,能夠理解爲其近似在自旋跑;此時此線程對cpu的使用權限是特別高的;別的線程壓根就搶不到cpu的時間片。咱們加了同步快以後,咱們此時線程會產生阻塞(cpu的使用權限被別的線程搶去了)。產生阻塞以後會發生線程上下文切換。以下:
可見性: 一個線程對某個共享主內存變量進行修改以後,其餘與此共享變量相關的線程會立馬感知到這個數據的更改。其餘線程能夠看到某個線程修改後的值。
以前代碼咱們發現,咱們兩個線程一個線程1修改掉flag的值以後,線程2是load讀取不到寫的值的,那麼爲了保證線程將簡單標記爲變量的可見性。咱們最簡單的方式是使用volatile關鍵字進行修改這個多線程共享的變量。
public class VolatileTest2 { static volatile boolean flag = false; public void refresh(){ this.flag = true; String threadName = Thread.currentThread().getName(); System.out.println("線程: "+threadName+" 修改共享變量flag爲"+flag); } public void load(){ String threadName = Thread.currentThread().getName(); while (!flag){ } System.out.println("線程: "+threadName+" 嗅探到flag狀態的改變"+" flag:"+flag); } public static void main(String[] args) { /** * 建立兩個線程 */ VolatileTest2 obj = new VolatileTest2(); Thread thread1 = new Thread(() -> { obj.refresh(); }, "thread1"); Thread thread2 = new Thread(() -> { obj.load(); }, "thread2"); thread2.start(); try { /** * 確保咱們線程2先執行 */ Thread.sleep(2000); }catch (Exception e){ e.printStackTrace(); } thread1.start(); } }
輸出結果以下:
線程: thread1 修改共享變量flag爲true 線程: thread2 嗅探到flag狀態的改變 flag:true
volatile底層原理
volatile是Java虛擬機提供的輕量級的同步機制
volatile語義有以下兩個做用:
volatile緩存可見性實現原理:
彙編代碼查看:
緩存一致性原理再次剖析:
線程1跟線程2都已經將flag=false的值加載到各自的工做內存,此時flag的狀態都是S狀態(共享狀態),此時線程2將修改flag的值爲true時候,其狀態變成了M狀態,這個時候線程1所在的cpu會嗅探到flag值修改讓後將flag對應的緩存行狀態設置爲I(無效狀態),而後咱們線程1須要使用的時候因爲值無效,須要從新加載,此時須要從新加載的話,須要線程2將修改的值添加到主內存,而後線程1纔可以加載到正確的值。
Java內存模型內存交互操做:
把一個變量從主內存中複製到工做內存中,就須要按順序地執行read個load操做,若是把變量從工做內存中同步到主內存中,就須要按照順序地執行 store個write操做。可是Java內存模型只要求上述操做必須按照順序執行,而沒有保證必須是連續執行的。
以上是順序性而不是連貫的,注意read跟load必須成對出現;store跟write必須成對出現。