文章是有點誤解別人的意思,但是在文章的評論裏面,作者也指出了錯誤。
我根據文章的錯誤之處和網友的評論,總結了自己的一些方法和思路。希望跟大家探討。
文章出處: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是用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來保證數據的一致性。
歡迎發表不同意見和看法,共同探討和交流。