JMM屏蔽了底層不同計算機的差別,描述了Java程式中線程共享變量的通路規則,以及在jvm中将變量存儲到記憶體和從記憶體中讀取變量這樣的底層細節。
JMM有以下規定:
所有的共享變量都存儲與主記憶體中,這裡所說的變量指的是執行個體變量和類變量,不包含局部變量,因為局部變量是線程私有的,是以不存在競争問題。
每一個線程還存在自己的工作記憶體,線程的工作記憶體,保留了被線程使用的變量的工作副本。
線程對變量的所有操作(讀和寫)都必須在工作記憶體中完成,而不能直接讀寫主記憶體中的變量。
不同線程之間也不能直接通路對方工作記憶體中的變量,線程間變量的值傳遞需要通過主記憶體中轉來完成。
多線程下變量的不可見性:
原因:

子線程t從主記憶體讀取到資料放入其對應的工作記憶體
将flag的值更改為true,但flag的值還沒有寫回主記憶體
此時main方法讀取到了flag的值為false
當子線程t将flag的值寫回主記憶體後,主線程沒有再去讀取主記憶體中的值,是以while(true)讀取到的值一直是false。
volite 可以實作并發下共享變量的可見性;
volite 不保證原子性;
volite 可以防止指令重排序的操作。
使用原子類來保證原子性:
有時為了提高性能,編譯器和處理器常常會對既定的代碼執行順序進行指令重排序。重排序可以提高處理的速度。
happens-before :前一個操作的結果可以被後續的操作擷取。
happens-before規則:
程式順序規則(單線程規則)
同一個線程中前面的所有寫操作對後面的操作可見
鎖規則(Synchronized,Lock等)
如果線程1解鎖了monitor a,接着線程2鎖定了a,那麼,線程1解鎖a之前的寫操作都對線程2可見(線程
1和線程2可以是同一個線程)
volatile變量規則:
如果線程1寫入了volatile變量v(臨界資源),接着線程2讀取了v,那麼,線程1寫入v及之前的寫操作都
對線程2可見(線程1和線程2可以是同一個線程)
傳遞性:
A h-b B , B h-b C 那麼可以得到 A h-b C
join()規則:
線程t1寫入的所有變量,在任意其它線程t2調用t1.join(),或者t1.isAlive() 成功傳回後,都對t2可見。
start()規則:
假定線程A在執行過程中,通過執行ThreadB.start()來啟動線程B,那麼線程A對共享變量的修改在接下來
線程B開始執行前對線程B可見。注意:線程B啟動之後,線程A在對變量修改線程B未必可見。
沒給b加volatile,那麼有可能出現a=1 , b = 3 。因為a雖然被修改了,但是其他線程不可見,而b恰好其他線程可見,造成了b=3 , a=1。
如果使用volatile修飾long和double,那麼其讀寫都是原子操作
餓漢式(靜态常量)
餓漢式(靜态代碼塊)
懶漢式(線程安全,性能差)
懶漢式(volatile雙重檢查模式,推薦)
此處加上volatile 的作用:
① 禁止指令重排序。
建立對象的過程要經過以下幾個步驟s:
a. 配置設定記憶體空間
b. 調用構造器,初始化執行個體
c. 傳回位址給引用
原因:由于建立對象是一個非原子操作,編譯器可能會重排序,即隻是在記憶體中開辟一片存儲空間後直接傳回記憶體的引用。而下一個線程在判斷 instance 時就不為null 了,但此時該線程隻是拿到了沒有初始化完成的對象,該線程可能會繼續拿着這個沒有初始化的對象繼續進行操作,容易觸發“NPE 異常”。
② 保證可見性
靜态内部類單例方式
靜态内部類隻有在調用時才會被加載,jvm在底層會保證隻有一個線程去初始化執行個體,下一個線程擷取執行個體時就直接傳回。
相比于雙重檢查,靜态内部類的代碼更簡潔。但基于volatile的雙重檢查有一個額外的優勢:除了可以對靜态字段實作延遲加載初始化外,還可以對執行個體字段實作延遲初始化。
volatile适合做多線程中的純指派操作:如果一個共享變量自始至終隻被各個線程指派,而沒有其他操作,那麼可以用volatile來代替synchronized,因為指派操作本身是原子性的,而volatile又保證了可見性,是以足以保證線程安全。
volatile可以作為重新整理之前變量的觸發器,可以将某個變量設定為volatile修飾,其他線程一旦發現該變量修改的值後,觸發擷取到該變量之前的操作都将是最新可見的。
volatile隻能修飾執行個體變量和類變量,而synchronized可以修飾方法,以及代碼塊。
volatile保證資料的可見性,但是不保證原子性,不保證線程安全。
volatile可以禁止指令重排序,解決單例雙重檢查對象初始化代碼執行亂序問題。
volatile可以看做輕量版synchronized,volatile不保證原子性,但是如果對一個共享變量隻進行純指派操作,而沒有其他操作,那麼可以使用volatile來代替synchronized,因為指派本身是有原子性的,而volatile又保證了可見性,是以就保證了線程安全。