1. 程式人生 > >volatile、synchronized、AtomicInteger多執行緒累加1000個計數的區別

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

歡迎發表不同意見和看法,共同探討和交流。