volatile不能保证原子性

 

转载自:https://www.cnblogs.com/dolphin0520/p/3920373.html

 从上面知道volatile关键字保证了操作的可见性,但是volatile能保证对变量的操作是原子性吗?

下面这个例子可以看出来volatile不能保证原子性,仅仅能保证可见性.


import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class MultiThreadDemo {
	
	
	public  volatile int inc = 0;
	public AtomicInteger atInc = new AtomicInteger();
	
	Lock lock = new ReentrantLock();
	
	//不能保证原子性
	public  void increase(){
		inc++;
	}

	//通过synchronized关键字实现原子性
	public synchronized void syncIncrease(){
		inc++;
	}

	//通过lock实现原子性
	public  void lockedIncrease(){
		lock.lock();
		try {
			inc++;
		} catch (Exception e) {
			// TODO: handle exception
		}finally{
			lock.unlock();
			
		}
	}
	
	//通过AtomicInteger实现
	public  void atIncrease(){
		atInc.getAndIncrement();
	}
	
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		for(int i=0;i<20;i++){
			doTest();
			
		}

	}
	
	
	public static void doTest(){
		final MultiThreadDemo demo = new MultiThreadDemo();
		
		for(int i=0;i<10;i++){
			new Thread(){
				public void run(){
					for(int j=0;j<100000;j++){
						//demo.increase();
						//demo.lockedIncrease();
						demo.atIncrease();
					}
				}
			}.start();
		}
		
		while(Thread.activeCount()>1){
			Thread.yield();
		}
		System.out.println(demo.atInc);
	}

	
	
	
	
}

大家想一下这段程序的输出结果是多少?也许有些朋友认为是10000。但是事实上运行它会发现每次运行结果都不一致,都是一个小于10000的数字。

  可能有的朋友就会有疑问,不对啊,上面是对变量inc进行自增操作,由于volatile保证了可见性,那么在每个线程中对inc自增完之后,在其他线程中都能看到修改后的值啊,所以有10个线程分别进行了1000次操作,那么最终inc的值应该是1000*10=10000。

  这里面就有一个误区了,volatile关键字能保证可见性没有错,但是上面的程序错在没能保证原子性。可见性只能保证每次读取的是最新的值,但是volatile没办法保证对变量的操作的原子性。

  在前面已经提到过,自增操作是不具备原子性的,它包括读取变量的原始值、进行加1操作、写入工作内存。那么就是说自增操作的三个子操作可能会分割开执行,就有可能导致下面这种情况出现:

  假如某个时刻变量inc的值为10,

  线程1对变量进行自增操作,线程1先读取了变量inc的原始值,然后线程1被阻塞了;

  然后线程2对变量进行自增操作,线程2也去读取变量inc的原始值,由于线程1只是对变量inc进行读取操作,而没有对变量进行修改操作,所以不会导致线程2的工作内存中缓存变量inc的缓存行无效,所以线程2会直接去主存读取inc的值,发现inc的值时10,然后进行加1操作,并把11写入工作内存,最后写入主存。

  然后线程1接着进行加1操作,由于已经读取了inc的值,注意此时在线程1的工作内存中inc的值仍然为10,所以线程1对inc进行加1操作后inc的值为11,然后将11写入工作内存,最后写入主存。

  那么两个线程分别进行了一次自增操作后,inc只增加了1。

  解释到这里,可能有朋友会有疑问,不对啊,前面不是保证一个变量在修改volatile变量时,会让缓存行无效吗?然后其他线程去读就会读到新的值,对,这个没错。这个就是上面的happens-before规则中的volatile变量规则,但是要注意,线程1对变量进行读取操作之后,被阻塞了的话,并没有对inc值进行修改。然后虽然volatile能保证线程2对变量inc的值读取是从内存中读取的,但是线程1没有进行修改,所以线程2根本就不会看到修改的值。

  根源就在这里,自增操作不是原子性操作,而且volatile也无法保证对变量的任何操作都是原子性的。

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值