天天看點

volatile

JMM屏蔽了底層不同計算機的差別,描述了Java程式中線程共享變量的通路規則,以及在jvm中将變量存儲到記憶體和從記憶體中讀取變量這樣的底層細節。

JMM有以下規定:

所有的共享變量都存儲與主記憶體中,這裡所說的變量指的是執行個體變量和類變量,不包含局部變量,因為局部變量是線程私有的,是以不存在競争問題。

每一個線程還存在自己的工作記憶體,線程的工作記憶體,保留了被線程使用的變量的工作副本。

線程對變量的所有操作(讀和寫)都必須在工作記憶體中完成,而不能直接讀寫主記憶體中的變量。

不同線程之間也不能直接通路對方工作記憶體中的變量,線程間變量的值傳遞需要通過主記憶體中轉來完成。

多線程下變量的不可見性:

原因:

volatile

子線程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又保證了可見性,是以就保證了線程安全。