1. 程式人生 > >Java多執行緒中的volatile變數

Java多執行緒中的volatile變數

首先,volatile的作用是保證記憶體的可見性,但是不能保證操作的原子性。

在Java中記憶體模型中,將記憶體模型分為主記憶體和工作記憶體

主記憶體是對所有執行緒所共享的,而每個執行緒都有自己的工作記憶體(比如cpu快取,暫存器等等都是一個原理的,都是為了加快讀取速度),工作記憶體是不共享的。

線上程工作時,會從主記憶體中拷貝一份需要的變數放在工作記憶體。執行緒對變數的所有操作,都是先操作工作記憶體中的副本,然後由副本對主記憶體進行同步更新。

執行緒間變數值的傳遞需要通過主記憶體來完成,而工作記憶體的變數何時同步到主記憶體中,這個就是由JVM來控制的。

由於這種儲存原理,在多執行緒中就會產生髒讀

(髒讀就是讀到的不是最新的資料)

比如:

int i = 0;
//執行緒A和執行緒B同時執行下面操作
i++;

正常來說,我們希望的值是2,但是輸出的值不一定是2,這就是因為髒讀。

原理是:A從主記憶體中拿去了一份i = 0;的拷貝到工作記憶體,在工作記憶體中進行i++;將i變為了1。但是在A將工作記憶體中的資訊同步到主記憶體之前,B執行緒已經從主記憶體中拿了一份拷貝放在了工作記憶體,此時B執行緒工作記憶體中的i = 0;執行 i++;後i = 1;。這樣兩個工作記憶體中的i都為1,所以最後同步到主記憶體中的值就是1,最終結果就為1.

上述例子中,執行緒A和執行緒B的工作記憶體是不可見的,也就是兩個互不干擾。

volatile能實現記憶體的可見性

所謂可見性就是指當一個執行緒修改共享變數,其他執行緒所獲取的變數值一定是最新的。原理就是volatile關鍵字修飾的變數,會保證修改後的值會立即更新到主存中,同時其他執行緒工作記憶體中儲存的備份會失效,當其他執行緒下次讀取該變數時,將強制去主存中拿取。

那麼問題來了,volatile能不能解決髒讀問題呢?答案是不能,因為volatile不能實習原子性操作。

所謂原子性操作就是不可能再拆分的操作,要麼執行,要麼不執行。比如synchronized修飾的方法或者程式碼塊就是原子性。在多程序(執行緒)訪問共享資源時,能夠確保所有其他的程序(執行緒)都不在同一時間內訪問相同的資源。簡單理解,原子性就是同時只有一個執行緒可以對該資源進行操作,並且不能被中斷。

而對於剛才的例子,即便volatile能保證執行緒A對i進行操作後立刻將主記憶體中的i變數進行同步更新,但是如果在A之前執行緒B已經獲取到了那麼就又會出現髒讀的情況。

所以在多執行緒中使用volatile變數要避免以下2點:

1,對變數的更改操作不依賴當前的值
比如:i = 10;//這個賦值語句就和i的值沒有關係,而i++就依賴i的值進行計算。
2.不能將變數應用在臨界值判斷上(上界或下界)

volatile與sychronized的區別
1.volatile只能使用在變數級別,sychronized可以使用在方法、程式碼塊上
2.volatile不會造成執行緒阻塞,sychronized會造成執行緒阻塞
3.volatile不能夠保證操作的原子性,sychronized可以保證操作的原子性