volatile、synchronized、AtomicInteger多執行緒累加1000個計數的區別
阿新 • • 發佈:2018-12-23
今天在網上看到一篇文章,談論的是根據volatile特性來用1000個執行緒不斷的累加數字,每次累加1個,到最後值確不是1000.
文章是有點誤解別人的意思,但是在文章的評論裡面,作者也指出了錯誤。
我根據文章的錯誤之處和網友的評論,總結了自己的一些方法和思路。希望跟大家探討。
文章出處:http://www.cnblogs.com/aigongsi/archive/2012/04/01/2429166.html
此文的問題是:1000個執行緒可能還有N個(例如50個)執行緒沒有執行完,主執行緒(main方法)就已經執行了,所以造成了最後的count值不是我們想要的值。就算等1000個執行緒執行完以後,再執行主執行緒的獲取count值,資料也不一定正確。因為volatile不能保證原子性,只能保證可見性。見下面分析。
關於volatile:
java語言規範描述:每一個變數都有一個主記憶體。為了保證最佳效能,JVM允許執行緒從主記憶體拷貝一份私有拷貝,然後線上程讀取變數的時候從主記憶體裡面讀,退出的時候,將修改的值同步到主記憶體。
根據上面提供的文章,用volatile好像能解決1000次累加的計算值。但是結果不是的。
首先說兩個概念:原子性和可見性
原子性,根據我個人的理解:當前變數只允許一個執行緒來操作,不接受多執行緒來訪問。所以每次的都是最新的值。
可見性,根據我個人的理解:變數t。A執行緒對t變數修改的值,對B執行緒是可見的。但是A獲取到t的值加1之後,突然掛起了,B獲取到的值還是最新的值,volatile能保證B能獲取到的t是最新的值,因為A的t+1並沒有寫到主記憶體裡面去。這個邏輯是沒有問題的。
回到上面的1000次累加的問題,變數count,1000次累加,1000個執行緒,volatile能保證的是每個執行緒讀取的變數的值在記憶體裡面是最新的,這個沒問題。
這1000個執行緒裡面,會有這樣的場景:
當第523個執行緒讀取的count值,假設這個值為522,執行緒把count加1後,count為523了,但是這個時候count值還沒有寫入到主記憶體裡面去,CPU在某種情況把第523個執行緒中止(掛起)了,這樣,第524個執行緒從主記憶體讀取的值還是522,當第524個執行緒把值寫入到主記憶體後,count值為523,然後第523個執行緒開始執行(這個時候第523個執行緒已經加好了count的值且值為523,只是沒有同步到主記憶體),把count值同步到主記憶體,這個時候,count的值還是523,第524個執行緒累加的值等於沒有累加。 所以造成了最後資料一定不是1000。
下面的程式碼是我個人的理解:
count.get()是AtomicInteger的值;
count是用volatile修飾的變數的值;
synNum是用synchronized修飾的值;
所以,用synchronized和AtomicInteger能保證是你想要的資料,volatile並不能保證。
第一次執行結果:
main
執行結果:Counter.count=1000,,,991,,,1000
第二次執行結果:
main
執行結果:Counter.count=1000,,,998,,,1000
第三次執行結果:
main
執行結果:Counter.count=1000,,,993,,,1000
可見,就算用了volatile,也不能保證資料是你想要的資料,volatile只能保證你資料的可見性(獲取到的是最新的資料,不能保證原子性,說白了,volatile跟原子性沒關係)
要保證原子性,對資料的累加,可以用AtomicInteger類;
也可以用synchronized來保證資料的一致性。
歡迎發表不同意見和看法,共同探討和交流。
文章是有點誤解別人的意思,但是在文章的評論裡面,作者也指出了錯誤。
我根據文章的錯誤之處和網友的評論,總結了自己的一些方法和思路。希望跟大家探討。
文章出處:http://www.cnblogs.com/aigongsi/archive/2012/04/01/2429166.html
此文的問題是:1000個執行緒可能還有N個(例如50個)執行緒沒有執行完,主執行緒(main方法)就已經執行了,所以造成了最後的count值不是我們想要的值。就算等1000個執行緒執行完以後,再執行主執行緒的獲取count值,資料也不一定正確。因為volatile不能保證原子性,只能保證可見性。見下面分析。
關於volatile:
java語言規範描述:每一個變數都有一個主記憶體。為了保證最佳效能,JVM允許執行緒從主記憶體拷貝一份私有拷貝,然後線上程讀取變數的時候從主記憶體裡面讀,退出的時候,將修改的值同步到主記憶體。
根據上面提供的文章,用volatile好像能解決1000次累加的計算值。但是結果不是的。
首先說兩個概念:原子性和可見性
原子性,根據我個人的理解:當前變數只允許一個執行緒來操作,不接受多執行緒來訪問。所以每次的都是最新的值。
可見性,根據我個人的理解:變數t。A執行緒對t變數修改的值,對B執行緒是可見的。但是A獲取到t的值加1之後,突然掛起了,B獲取到的值還是最新的值,volatile能保證B能獲取到的t是最新的值,因為A的t+1並沒有寫到主記憶體裡面去。這個邏輯是沒有問題的。
回到上面的1000次累加的問題,變數count,1000次累加,1000個執行緒,volatile能保證的是每個執行緒讀取的變數的值在記憶體裡面是最新的,這個沒問題。
這1000個執行緒裡面,會有這樣的場景:
當第523個執行緒讀取的count值,假設這個值為522,執行緒把count加1後,count為523了,但是這個時候count值還沒有寫入到主記憶體裡面去,CPU在某種情況把第523個執行緒中止(掛起)了,這樣,第524個執行緒從主記憶體讀取的值還是522,當第524個執行緒把值寫入到主記憶體後,count值為523,然後第523個執行緒開始執行(這個時候第523個執行緒已經加好了count的值且值為523,只是沒有同步到主記憶體),把count值同步到主記憶體,這個時候,count的值還是523,第524個執行緒累加的值等於沒有累加。 所以造成了最後資料一定不是1000。
下面的程式碼是我個人的理解:
import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; public class Counter { public static AtomicInteger count = new AtomicInteger();//原子操作 public static CountDownLatch latch= new CountDownLatch(1000);//執行緒協作處理 public static volatile int countNum = 0;//volatile 只能保證可見性,不能保證原子性 public static int synNum = 0;//同步處理計算 public static void inc() { try { Thread.sleep(1); } catch (InterruptedException e) { } countNum++; int c = count.addAndGet(1); add(); System.out.println(Thread.currentThread().getName() + "------>" + c); } public static synchronized void add(){ synNum++; } public static void main(String[] args) { //同時啟動1000個執行緒,去進行i++計算,看看實際結果 for (int i = 0; i < 1000; i++) { new Thread(new Runnable() { @Override public void run() { Counter.inc(); latch.countDown(); } },"thread" + i).start(); } try { latch.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()); System.out.println("執行結果:Counter.count=" + count.get() + ",,," + countNum + ",,," + synNum); }
count.get()是AtomicInteger的值;
count是用volatile修飾的變數的值;
synNum是用synchronized修飾的值;
所以,用synchronized和AtomicInteger能保證是你想要的資料,volatile並不能保證。
第一次執行結果:
main
執行結果:Counter.count=1000,,,991,,,1000
第二次執行結果:
main
執行結果:Counter.count=1000,,,998,,,1000
第三次執行結果:
main
執行結果:Counter.count=1000,,,993,,,1000
可見,就算用了volatile,也不能保證資料是你想要的資料,volatile只能保證你資料的可見性(獲取到的是最新的資料,不能保證原子性,說白了,volatile跟原子性沒關係)
要保證原子性,對資料的累加,可以用AtomicInteger類;
也可以用synchronized來保證資料的一致性。
歡迎發表不同意見和看法,共同探討和交流。