volatile与synchronized原理分析

一、原子性、可见性

        volatile与synchronized都具有原子性和可见性,但底层实现不同。

    1、线程间的通信-共享数据

        介绍原子性和可见性之前,先介绍一下共享数据如何在线程间通信。

        实例是存在堆内存中的,堆内存在线程间共享。局部变量、方法不会在线程间共享,所以不会有可见性问题。共享变量存储在堆内存中,为了加快执行效率,每个线程都有一块私有的本地内存,私有的本地内存存储了共享变量的副本。接下来线程从本地内存中读取共享变量,不从堆内存中读取。例如两个线程,线程A在修改共享变量后,会刷新堆内存的共享变量,线程B也会同步堆内存的值。但是线程A具体什么时候刷新堆内存的数据是不确定的,线程B什么时候同步堆内存的值也是不确定的。

        3c53960e1ecba21e9240f56deab51a5b077.jpg

    2、volatile原子性、可见性的实现

        对于声明了volatile的变量进行写操作的时候,JVM会把这个变量所在的缓存行数据立即写回到堆内存。同时,JMM将其他线程本地内存中的共享变量的值会置为失效,其他线程在读取变量时,会从堆内存中重新读取。

        volatile对变量的单个读/写具有原子性,但不能做到复合操作的原子性,例如volatile++。如果要实现复合操作的原子性,建议使用java.util.concurrent.atomic包下的Atomic类;

    3、synchronized原子性、可见性实现

        synchronized是将锁释放后,会把数据同步到堆内存中;另一个线程获取锁后,会从堆内存同步共享数据到线程本地内存中。

        现在通过使用javap工具查看生成的class文件信息来分析synchronized关键字的实现细节。

public class SynchronizedJVM {

	public void method1() {
		synchronized (this) {
		}
	}
	
	public synchronized void method2() {
		
	}
	
	
}

        在class路径下执行javap -v SynchronizedJVM.class:

{
  public com.study.multiThread.SynchronizedJVM();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #8                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/study/multiThread/SynchronizedJVM;

  public void method1();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: dup
         2: monitorenter
         3: monitorexit
         4: return
      LineNumberTable:
        line 6: 0
        line 8: 4
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/study/multiThread/SynchronizedJVM;

  public synchronized void method2();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED
    Code:
      stack=0, locals=1, args_size=1
         0: return
      LineNumberTable:
        line 12: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       1     0  this   Lcom/study/multiThread/SynchronizedJVM;
}

        可以看到synchronized同步块的实现使用了monitorenter和monitorexit指令,而同步方法是依靠方法修饰符上的ACC_SYNCHRONIZED来完成的。无论采用哪种方式,本质是对一个对象的监视器(monitor)进行获取,而这个获取过程是排他的,也就是同一时刻只能有一个线程获取到synchronized所保护的对象监视器。线程想要执行同步块中的方法,先要通过monitorenter获得对象的监视器,如果失败,就进入对象的同步队列,线程进入blocked状态;如果成功,线程就可以进入同步代码块。获得锁的线程执行monitorexit指令后,会唤醒阻塞在同步队列中的线程,同步队列中的线程再次通过monitorenter争夺对象的监视器(monitor)。

二、volatile禁止指令重排序

        volatile禁止指令重排序,典型的应用是单例模式中的双重检查机制。

        由于synchronized是一个重量级锁,会影响运行效率。双重检查机制中,先判断单例变量是否为null,再进入同步代码块。这样只在第一次获取单例变量时,会有效率影响。

public class DoubleCheckSingleton {

	private volatile static DoubleCheckSingleton singleton;
	
	private DoubleCheckSingleton() {}
	
	public static DoubleCheckSingleton getInstance() {
		if(singleton == null) {
			synchronized (singleton) {
				if(singleton == null) {
					singleton = new DoubleCheckSingleton();
				}
			}
		}
		return singleton;
	}
	
}

        单例变量需要用volatile修饰,否则,在new单例对象时会出现问题。使用new来创建一个对象可以分解为如下的3行伪代码:

	memory = allocate();   //1.分配对象的内存空间
	ctorInstance(memory);  //2.初始化对象
	instance = memory; 	   //3.设置instance指向刚分配的内存地址

        上面3行伪代码中的2和3之间可能会发生重排序,排序后的执行顺序如下:

	memory = allocate();   //1.分配对象的内存空间
	instance = memory; 	   //3.设置instance指向刚分配的内存地址,此时对象还没有初始化,但instance == null 判断为false
	ctorInstance(memory);  //2.初始化对象

        指令重排序后,在多线程情况下,可能会发生A线程正在new对象,执行了3,但还没有执行2。此时B线程进入方法获取单例对象,执行同步代码块外的非空判断,发现变量非空,但此时对象还未初始化,B线程获取到的是一个未被初始化的对象。

        使用volatile修饰后,禁止指令重排序。即,先初始化对象后,再设置instance指向刚分配的内存地址。这样就就不存在获取到未被初始化的对象。

三、其他区别

  1. volatile仅能使用在变量级别;synchronized则可以使用在代码块和方法上;
  2. volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。

转载于:https://my.oschina.net/u/2896303/blog/1838287

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值