volatile、synchronized、AtomicInteger多線程累加1000個計數的區別

今天在網上看到一篇文章,談論的是根據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。

下面的代碼是我個人的理解:


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來保證數據的一致性。

歡迎發表不同意見和看法,共同探討和交流。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章