/** * volatile變量自增運算測試 * 當一個變量定義爲volatile以後,它將具有兩種特性 * 第一 是保證此變量對全部線程的可見性 * volatile變量在各個線程的工做內存中不存在一致性問題 * (在各個線程的工做內存中,volatile變量也能夠存在不一致的狀況,但因爲每次使用以前都要先刷新,執行引擎看不到不一致的狀況, * 所以能夠認爲不存在一致性問題),可是Java裏面的運算並不是原子操做,致使volatile變量的運算在併發下同樣是不安全的。 * 因爲volatile變量只能保證可見性,在不符合如下兩條規則的運算場景中,仍然要經過加鎖(使用synchronized或java.util.concurrent類) * 來保證原子性。 * a、運算結果並不依賴變量的當前值,或者可以確保只有單一的線程修改變量的值。 * b、變量不須要與其它的狀態變量共同參與不變約束。 * 第二 禁止指令重排序優化 */ public class VolatileTest { public static volatile int race = 0; public static void increase() { race++; } private static final int THREADS_COUNT = 20; public static void main(String[] args) { Thread[] threads = new Thread[THREADS_COUNT]; for (int i = 0; i < THREADS_COUNT; i++) { threads[i] = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 10000; i++) { increase(); } } }); threads[i].start(); } // 等待全部累加線程都結束 while (Thread.activeCount() == 1) { Thread.yield(); } System.out.println("[VolatileTest] race= " + race); } }
# javap -c VolatileTest.classjava
反編譯部分截圖安全
注:即便編譯出來只有一條字節碼指令,也並不意味執行這條指令就是一個原子操做。一條字節碼指令在解釋執行時,解釋器將要運行許多行代碼才能實現它的語義,若是是編譯執行,一條字節碼指令也可能轉化成若干條本地機器碼指令。併發
public class Singleton { private volatile static Singleton instance; public static Singleton getSingleton() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } public static void main(String[] args) { Singleton.getSingleton(); } }