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来保证数据的一致性。

欢迎发表不同意见和看法,共同探讨和交流。

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。