Java volatile 關鍵字

Java volatile 關鍵字

在Java中,valatile關鍵字被用來標識一個Java變量 ”being stored in main memory“。更準確地講,每次讀取一個volatile變量都會從主存中去讀,而不是從CPU緩存中;每次修改一個volatile變量都會將其寫入到主存中,而不僅僅是CPU緩存中。

保證內存可見性

在一個多線程的程序中,多個線程操作一個非volatile修飾的變量,每個線程可能會把變量從主存複製到CPU緩存中去,處於性能原因,如果你的計算機裝有多個CPU,每個線程可能運行在不同的CPU上。這也就意味着,每個線程可能將變量拷貝到不同CPU到的CPU緩存中去。如下圖:

volatile

對於非volatile變量,Java虛擬機將數據從主存讀入到CPU緩存,或者從CPU緩存寫回到主存中並沒有任何保證。

看這樣的一個例子,多個線程訪問一個包含一個counter變量的共享對象,聲明如下:

public class SharedObject{
    public int counter = 0;
}

線程1讀取共享counter變量(值爲0)到它的CPU的緩存中,然後將counter增加到1,沒有將變化後的值寫回到主存中。線程2做了和線程1同樣的行爲。線程1和線程2並沒有進行同步。counter變量實際的值應該爲2,但是每個線程在它們的CPU緩存中讀到的變量的值爲1,在主存中counter變量的值還是0.儘管線程最終會將counter變量的值寫回到主存中,但值將會是錯誤的。

如果將counter聲明爲volatile,JVM會保證每次都從主存中讀取變量,每次修改過值後都會寫回到主存中。聲明如下:

public class SharedObject{
    public volatile int counter = 0;
}

在某些場景下,將變量簡單聲明爲volatile就可以完全確保多個線程訪問這個變量時可以最後被修改的值。

在兩個線程都讀和寫同一個變量時,簡單的將變量聲明爲volatile是不夠的。
在CPU1中,線程1可能將counter變量(值爲0)讀入到一個CPU寄存器中。與此同時(或者稍靠後),在CPU2中,線程2可能將counter變量(值爲0)讀入到一個CPU寄存器中。兩個線程都從主存中讀取變量。現在,都將變量的值加1,然後寫回到主存中。它們都增加counter的寄存器版本爲1,都將值1寫回到主存中。經過兩次自增後counter變量的值應該爲2.

這個多線程的問題就是沒有看到變量最後被修改後的值因爲值還沒有被寫回到主存中,一個線程的更新對其它線程是不可見的。

volatile

實際上,volatile保證了兩點:
- 內存可見性
- 防止指令重排序

volatile的適用場景

如果一個線程讀和寫一個volatile變量,其它線程只讀取這個變量,然後讀線程可以確保看到這個變量最後的被修改的值。如果這個變量不被修改爲volatile,將沒有這樣的擔保。

volatile的性能考慮
  • 發生在主存上的讀寫操作的代價要高於訪問CPU緩存
  • volatile阻止了指令重排序這種性能優化技術
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章