原創轉載請注明出處:https://www.cnblogs.com/agilestyle/p/11426473.html
關鍵字volatile的主要作用是使變量在多個線程間可見,但無法保證原子性,對於多個線程訪問同一個實例變量需要加鎖進行同步。
1 package org.fool.java.concurrent.volatiletest; 2 3 import java.util.concurrent.ExecutorService; 4 import java.util.concurrent.Executors; 5 6 public class VolatileTest1 { 7 8 private static volatile int count = 0; 9 10 private static void addCount() { 11 for (int i = 0; i < 100; i++) { 12 count++; 13 } 14 15 System.out.println(Thread.currentThread().getName() + " count = " + count); 16 } 17 18 public static void main(String[] args) { 19 ExecutorService executor = Executors.newCachedThreadPool(); 20 21 for (int i = 0; i < 100; i++) { 22 executor.execute(new Runnable() { 23 @Override 24 public void run() { 25 VolatileTest1.addCount(); 26 } 27 }); 28 } 29 30 executor.shutdown(); 31 } 32 }
Note:
addCount()方法沒有加synchronized
Console Output
預期結果應該是10000,盡管count被volatile修飾,保證了可見性,但是count++並不是一個原子性操作,它被拆分為load、use、assign三步,而這三步在多線程環境中,use和assgin是多次出現的,但這操作是非原子性的,也就是在read和load之后,如果主內存count變量發生修改之后,線程工作內存中的值由於已經加載,不會產生對應的變化,也就是私有內存和公有內存中的變量不同步,所以計算出來的值和預期不一樣,就產生了線程安全的問題,所以需要用synchronized進行加鎖同步
addCount()方法用synchronized進行加鎖同步
Console Output
結果10000與預期一致
所以volatile只能保證可見性不能保證原子性,但用volatile修飾long和double可以保證其操作原子性。
所以從Oracle Java Spec里面可以看到:
- 對於64位的long和double,如果沒有被volatile修飾,那么對其操作可以不是原子的。在操作的時候,可以分成兩步,每次對32位操作。
- 如果使用volatile修飾long和double,那么其讀寫都是原子操作
- 對於64位的引用地址的讀寫,都是原子操作
- 在實現JVM時,可以自由選擇是否把讀寫long和double作為原子操作
- 推薦JVM實現為原子操作
Reference
http://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.7
http://www.cnblogs.com/louiswong/p/5951895.html
http://www.infoq.com/cn/articles/java-memory-model-4/