Java 基于volatile关键字修饰的变量在并发中不一致性的问题

这几天在复习多线程及高并发的相关知识。volatile关键字一般认为是一种较轻量级的多线程并发解决方案。确实,使用volatile关键字所修饰的变量能够保证变量值一旦更新则所有线程都能够看到最新的值,但这并不意味着使用volatile关键字修饰的变量在做运算操作时能够保证数据的一致性,因为Java的运算操作并非为原子操作。下面我们举个例子:

public class test {
	
	public static volatile int increase = 0;
	
	public static void main(String[] args) throws Exception {
		 
		Thread[] threads = new Thread[20];
		for (Thread thread : threads) {
			thread = new Thread(new Runnable() {				
				@Override
				public void run() {
					for (int i = 0; i < 10000; i++) {
						increase++;		
					}							
				}
			});
			thread.start();
		}
		while (Thread.activeCount() > 1) {
			Thread.yield();
		}
		System.out.println("输出结果为 ==== " + increase);
    }
}

这里我们创建20个线程每个线程对同一个用volatile修饰的变量increase进行累加操作,一般认为此时获取的结果应为20000,但是运行的结果却为:

输出结果为 ==== 71670
输出结果为 ==== 60792
输出结果为 ==== 75454
输出结果为 ==== 72345
输出结果为 ==== 75036

运行5次的结果不但每次都不一样,而且数值没有一次等于20000。这是由volatile关键字的运行原理所致,由volatile关键字所修饰的变量,当某一线程需要使用它的时候会重新获取/刷新对应的变量一遍,然后再进行操作。但是,上面讲过,Java的运行操作并非原子性,因此从获取最新值到执行操作之间该变量仍有可能没其他线程修改,而这时候Java是不会再对该变量进行刷新操作的,因此才会出现上述执行结果。

现在我们再由单例模式解释下,单例模式代码如下:

/**
 * 单例模式
 * @author aron
 *
 */
public class Single implements Serializable{
	
	private static final long serialVersionUID = 1L;

	private static volatile Single single;
	
	public static Single getInstance(){
		if (single == null) {
			synchronized (Single.class) {
				if (single == null) {
					single = new Single();
				}
			}
		}
		return single;
	}
	
	private Single() {
	}

	private Object readResolve(){
		return single;
	}

}

在这里为什么我们在创建对象前要进行两次对single进行非空判断呢?原因就是volatile修饰的single在第一次判断null的时候首先更新single然后再判断是否为null,而在获取值与判断之间,single对象有可能被其他线程操作,因此判断之后需要对单例类进行加锁,并且再次判断single有没有在第一次获取single和判断null之间有没有被操作,因为这里使用了synchronized上锁,这里的判断不需要担心被其他线程所修改。最后通过查看字节码的方式巩固下:

public class designModel.singleton.Single implements java.io.Serializable {
  public static designModel.singleton.Single getInstance();
    Code:
       0: getstatic     #1       <========== 这里更新single变量               
                                 <========== 在判断之前有可能其他线程对single进行操作
       3: ifnonnull     37       <========== 这里进行第一次非null判断
       6: ldc           #2                  
       8: dup
       9: astore_0
      10: monitorenter           <========== 进入同步(synchronzied)模块
      11: getstatic     #1       <========== 这里更新single变量             
				 <========== 因为这里对类进行加锁,在第二次非null判断之间不需要担心single被其他线程操作
      14: ifnonnull     27       <========== 这里进行第二次非null判断
      17: new           #2       <========== 创建single实例           
      20: dup
      21: invokespecial #3                  
      24: putstatic     #1                  
      27: aload_0
      28: monitorexit
      29: goto          37
      32: astore_1
      33: aload_0
      34: monitorexit
      35: aload_1
      36: athrow
      37: getstatic     #1                  
      40: areturn
    Exception table:
       from    to  target type
          11    29    32   any
          32    35    32   any
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值