java虛擬機(十)synchronized是如何實現可見性和有序性的,和volatile的區別

大家都知道 synchronized 是鎖。那怎麼會實現可見性和有序性。volatile也能實現對吧。

java內存模型是這麼規定的

關於主內存與工作內存之間的交互協議,即一個變量如何從主內存拷貝到工作內存。如何從工作內存同步到主內存中的實現細節。java內存模型定義了8種操作來完成。這8種操作每一種都是原子操作。8種操作如下:

  • lock(鎖定):作用於主內存,它把一個變量標記爲一條線程獨佔狀態;
  • unlock(解鎖):作用於主內存,它將一個處於鎖定狀態的變量釋放出來,釋放後的變量才能夠被其他線程鎖定;
  • read(讀取):作用於主內存,它把變量值從主內存傳送到線程的工作內存中,以便隨後的load動作使用;
  • load(載入):作用於工作內存,它把read操作的值放入工作內存中的變量副本中;
  • use(使用):作用於工作內存,它把工作內存中的值傳遞給執行引擎,每當虛擬機遇到一個需要使用這個變量的指令時候,將會執行這個動作;
  • assign(賦值):作用於工作內存,它把從執行引擎獲取的值賦值給工作內存中的變量,每當虛擬機遇到一個給變量賦值的指令時候,執行該操作;
  • store(存儲):作用於工作內存,它把工作內存中的一個變量傳送給主內存中,以備隨後的write操作使用;
  • write(寫入):作用於主內存,它把store傳送值放到主內存中的變量中。

Java內存模型還規定了執行上述8種基本操作時必須滿足如下規則:

  • 不允許read和load、store和write操作之一單獨出現,以上兩個操作必須按順序執行,但沒有保證必須連續執行,也就是說,read與load之間、store與write之間是可插入其他指令的。
  • 不允許一個線程丟棄它的最近的assign操作,即變量在工作內存中改變了之後必須把該變化同步回主內存。
  • 不允許一個線程無原因地(沒有發生過任何assign操作)把數據從線程的工作內存同步回主內存中。
  • 一個新的變量只能從主內存中“誕生”,不允許在工作內存中直接使用一個未被初始化(load或assign)的變量,換句話說就是對一個變量實施use和store操作之前,必須先執行過了assign和load操作。
  • 一個變量在同一個時刻只允許一條線程對其執行lock操作,但lock操作可以被同一個條線程重複執行多次,多次執行lock後,只有執行相同次數的unlock操作,變量纔會被解鎖。
  • 如果對一個變量執行lock操作,將會清空工作內存中此變量的值,在執行引擎使用這個變量前,需要重新執行load或assign操作初始化變量的值。
  • 如果一個變量實現沒有被lock操作鎖定,則不允許對它執行unlock操作,也不允許去unlock一個被其他線程鎖定的變量。
  • 對一個變量執行unlock操作之前,必須先把此變量同步回主內存(執行store和write操作)。

 

可以看到最後一條synchronized 實現其實是靠lock和unlock實現的。在對一個變量unlock操作前。必須也先把此變量同步回主內存中。這就實現了可見性

 

而有序性是一個變量在同一個時刻只允許一個線程對其進行lock。這就說明了持有一個鎖的兩個同步塊只能串行進行。

 

 

再來說下volatile。

他的可見性是在對變量進行use動作前必須在它前面進行load,read動作

在對變量進行assign動作後必須連續進行store,write動作。

也就是說,使用前先讀取變量在內存的值,並且載入到工作內存副本中

賦值後要馬上寫回內存

而有序性是通過內存屏障來實現的。(重排序時不能把後面的指令重排序到內存屏障之前)

編譯器在生成字節碼時,會在指令序列中插入內存屏障來禁止特定類型的處理器重排序。(首先保證了正確性,再去追求執行效率)
1.在每個volatile寫操作前插入StoreStore屏障;對於這樣的語句Store1; StoreLoad; Store2,在Store2及後續寫入操作執行前,保證Store1的寫入操作對其它處理器可見。
2.在每個volatile寫操作後插入StoreLoad屏障;對於這樣的語句Store1; StoreLoad; Load2,在Load2及後續所有讀取操作執行前,保證Store1的寫入對所有處理器可見。
3.在每個volatile讀操作前插入LoadLoad屏障;對於這樣的語句Load1;LoadLoad; Load2,在Load2及後續讀取操作要讀取的數據被訪問前,保證Load1要讀取的數據被讀取完畢。
4.在每個volatile讀操作後插入LoadStore屏障;對於這樣的語句Load1; LoadStore; Store2,在Store2及後續寫入操作被刷出前,保證Load1要讀取的數據被讀取完畢。
如果編譯器無法確定後面是否還會有volatile讀或者寫的時候,爲了安全,編譯器通常會在這裏插入一個StoreLoad屏障
 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章