標籤:
鎖提供了兩種主要特性:互斥(mutual exclusion) 和可見度(visibility)。互斥即一次只允許一個線程持有某個特定的鎖,因此可使用該特性實現對共用資料的協調訪問協議,這樣,一次就只有一個線程能夠使用該共用資料。可見度要更加複雜一些,它必須確保釋放鎖之前對共用資料做出的更改對於隨後獲得該鎖的另一個線程是可見的 —— 如果沒有同步機制提供的這種可見度保證,線程看到的共用變數可能是修改前的值或不一致的值,這將引發許多嚴重問題。
如下:
import java.util.concurrent.CountDownLatch;import java.util.logging.Level;import java.util.logging.Logger;/** * * @author Kangjun */public class Counter { private volatile static int volatileValue = 0; private static final CountDownLatch latch = new CountDownLatch(1000); public static void inc() { try { Thread.sleep(1*1000); } catch (InterruptedException ex) { Logger.getLogger(Counter.class.getName()).log(Level.SEVERE, null, ex); } volatileValue++; } public static void main(String[] args) throws InterruptedException { for (int j = 0; j < 1000; j++) { new Thread(() -> { inc(); latch.countDown(); }).start(); } latch.await(); System.out.println("volatileValue:" + volatileValue); }}
輸出為:
volatileValue:993
為什麼不是1000呢?
讓我們來看一下jvm運行時刻的記憶體配置。其中有一個記憶體地區是jvm虛擬機器棧,每一個線程運行時都有一個線程棧,線程棧儲存了線程運行時候變數值資訊。當線程訪問某一個對象時候值的時候,首先通過對象的引用找到對應在堆記憶體的變數的值,然後把堆記憶體變數的具體值load到執行緒區域記憶體中,建立一個變數副本,之後線程就不再和對象在堆記憶體變數值有任何關係,而是直接修改副本變數的值,在修改完之後的某一個時刻(線程退出之前),自動把線程變數副本的值回寫到對象在堆中變數。這樣在堆中的對象的值就產生變化了。下面一幅圖:
其中use and assign 可以多次出現,但是這一些操作並不是原子性,也就是 在read load之後,如果主記憶體count變數發生修改之後,線程工作記憶體中的值由於已經載入,不會產生對應的變化,所以計算出來的結果會和預期不一樣,對於volatile修飾的變數,jvm虛擬機器只是保證從主記憶體載入到線程工作記憶體的值是最新的。
例如假如線程1,線程2 在進行read,load 操作中,發現主記憶體中count的值都是5,那麼都會載入這個最新的值
線上程1堆count進行修改之後,會write到主記憶體中,主記憶體中的count變數就會變為6
線程2由於已經進行read,load操作,在進行運算之後,也會更新主記憶體count的變數值為6
導致兩個線程及時用volatile關鍵字修改之後,還是會存在並發的情況。
參考:http://www.ibm.com/developerworks/cn/java/j-jtp06197.html
http://www.cnblogs.com/aigongsi/archive/2012/04/01/2429166.html
Java 中 volatile 關鍵字的使用注意點