volatile、synchronized、AtomicInteger对数据进行累加的差别

今天在网上看到一篇文章,谈论的是根据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来保证数据的一致性。

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

时间: 2024-10-17 22:39:43

volatile、synchronized、AtomicInteger对数据进行累加的差别的相关文章

hbase中double类型数据做累加

public static Result incr(String tableFullName, String rowKey, String family, String qualifier, long amount) throws IOException { Table table = HBaseConnectionFactory.getConnection().getTable(TableName.valueOf(tableFullName)); Increment increment = n

多线程之wait,notify,volatile,synchronized,sleep

最近在学习多线程,现在进行总结一下吧.首先要了解一下以下几个名词. (1)wait:当线程调用wait()方法时,当前该线程会进入阻塞状态,且释放锁,使用wait方法的时候,必须配合synchronized使用. (2)notify:当线程调用notify()方法时,会唤醒一个处于等待该对象锁的线程,不释放锁,使用notify方法的时候,必须配合synchronized使用. (3)sleep:当线程调用sleep()方法时,会让出CPU执行权,不释放锁.当指定的时间到了后,会自动恢复运行状态.

Java自增原子性问题(测试Volatile、AtomicInteger)

这是美团一面面试官的一个问题,后来发现这是一道面试常见题,怪自己没有准备充分:i++;在多线程环境下是否存在问题?当时回答存在,接着问,那怎么解决?...好吧,我说加锁或者synchronized同步方法.接着问,那有没有更好的方法? 经过一番百度.谷歌,还可以用AtomicInteger这个类,这个类提供了自增.自减等方法(如i++或++i都可以实现),这些方法都是线程安全的. 一.补充概念 1.什么是线程安全性? <Java Concurrency in Practice>中有提到:当多个

Java之voliate, synchronized, AtomicInteger使用

1: voliate 用在多线程,同步变量. 线程为了提高效率,将成员变量(如A)某拷贝了一份(如B),线程中对A的访问其实访问的是B.只在某些动作时才进行A和B的同步.因此存在A和B不一致的情况.volatile就是用来避免这种情况的.volatile告诉jvm, 它所修饰的变量不保留拷贝,直接访问主内存中的(也就是上面说的A) ,但是不能用其来进行多线程同步控制 public class Counter {     public volatile  static int count = 0;

volatile&amp;synchronized&amp;diff

volatile和synchronized特点 首先需要理解线程安全的两个方面:执行控制和内存可见. 执行控制的目的是控制代码执行(顺序)及是否可以并发执行. 内存可见控制的是线程执行结果在内存中对其它线程的可见性.根据Java内存模型的实现,线程在具体执行时,会先拷贝主存数据到线程本地(CPU缓存),操作完成后再把结果从线程本地刷到主存. synchronized关键字解决的是执行控制的问题,它会阻止其它线程获取当前对象的监控锁,这样就使得当前对象中被synchronized关键字保护的代码块

【C/C++学院】0826-文件重定向/键盘输入流/屏幕输出流/字符串输入输出/文件读写简单操作/字符文件读写二进制与文本差别/get与getline挖掘数据/二进制与文本差别/随机位置/多线程初级

文件重定向 #include<iostream> using namespace std; void main() { char str[30] = { 0 }; cin >> str; cout << str; system(str); cerr << "error for you"; cin.get(); cin.get(); } 键盘输入流 #include<iostream> #include <stdlib.h

数据仓库和Hadoop大数据平台有什么差别?

广义上来说,Hadoop大数据平台也可以看做是新一代的数据仓库系统, 它也具有很多现代数据仓库的特征,也被企业所广泛使用.因为MPP架构的可扩展性,基于MPP的数据仓库系统有时候也被划分到大数据平台类产品. 但是数据仓库和Hadoop平台还是有很多显著的不同.针对不同的使用场景其发挥的作用和给用户带来的体验也不经相同.用户可以根据下表简单判断什么场景更适合用什么样的产品.  数据仓库和Hadoop大数据平台特性比较 特性 Hadoop Data Warehouse 计算节点数 可到数千个 一般在

volatile、synchronized、AtomicInteger多线程累加1000个计数的区别

今天在网上看到一篇文章,谈论的是根据volatile特性来用1000个线程不断的累加数字,每次累加1个,到最后值确不是1000. 文章是有点误解别人的意思,但是在文章的评论里面,作者也指出了错误. 我根据文章的错误之处和网友的评论,总结了自己的一些方法和思路.希望跟大家探讨. 文章出处:http://www.cnblogs.com/aigongsi/archive/2012/04/01/2429166.html 此文的问题是:1000个线程可能还有N个(例如50个)线程没有执行完,主线程(mai

Synchronized、volatile与锁

时间尚早,今天多写一点 温故知新(三) Synchronized与volatile Synchronized有以下两个含义: 一.一次只有一个线程可以执行代码的受保护部分 二.一个线程更改的数据对于其他线程是可见的 volatile只适合于控制对基本变量(int.boolean等)的单个实例的访问.当一个变量被声明为volatile,任何对该变量的写操作都会绕过高速缓存,直接写入主内存,而任何对改变量的读取也都绕过高速缓存,直接取自主内存.这表示所有线程在任何时候看到的volatile变量值都相