欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java中的volatile关键字详解及单例模式双检锁问题分析

程序员文章站 2022-07-14 09:10:15
...

【参考文献】http://www.cnblogs.com/dolphin0520/p/3920373.html

看了好多关于volatile关键字的文章,这篇应该是讲得最清楚的了吧,从Java内存模型出发,结合并发编程中的原子性、可见性、有序性三个角度分析了volatile所起的作用,并从汇编角度大致说了volatile的原理,说明了该关键字的应用场景;我在这补充一点,分析下volatile是怎么在单例模式中避免双检锁出现的问题的。

一、先总结下并发编程中3个条件:

1、原子性:要实现原子性方式较多,可用synchronized、lock加锁,AtomicInteger等,但volatile关键字是无法保证原子性的;

2、可见性:要实现可见性,也可用synchronized、lock,volatile关键字可用来保证可见性;

3、有序性:要避免指令重排序,synchronized、lock作用的代码块自然是有序执行的,volatile关键字有效的禁止了指令重排序,实现了程序执行的有序性;

二、单例设计模式中的双检锁形式

public class Singleton{   
  private static Singleton instance;    //声明静态的单例对象的变量   
  private Singleton(){}    //私有构造方法    
     
  public static Singleton getInstance(){    //外部通过此方法可以获取对象     
    if(instance== null){      
        synchronized (Singleton.class) {   //保证了同一时间只能只能有一个对象访问此同步块         
            if(instance == null){       
                instance = new Singleton();           
        }      
      }   
    }     
    return instance;   //返回创建好的对象    
  }   
} 

双检锁方式避免了懒汉式加重量级锁synchronized,看似一种非常聪明的做法,但是这种写法在某些时候会出现错误,具体分析如下:

在创建对象时,执行instance=new Singleton()语句时,考虑执行下面的代码:

mem = allocate();             //Allocate memory for Singleton object.
instance = mem;               //Note that instance is now non-null, but has not been initialized.
ctorSingleton(instance);      //Invoke constructor for Singleton passing  instance.
step1: 先申请一块存储空间
step2: 将引用指向存储空间
step3: 调用构造器初始化该空间

以上执行顺序是完全可能出现的,在理想情况下,按照1->3->2的步骤执行时,双检锁形式可以正常工作,但是由于Java内存模型,允许重排序,所以完全可能按照1->2->3的顺序执行,则导致双检锁形式出现问题。即线程1在执行single=new Singleton()语句时,刚好按照1->2->3的顺序执行到step2处,此时,instance指向mem内存区域,而该内存区域未被初始化;同时,线程2在第一个if(instance==null)地方发现instance不为null了,于是得到这个为被初始化的实例instance进行使用,导致错误。因此,双检锁的单例模式成了学术实践而已。


三、双检锁单例模式的升级---采用volatile关键字
双检锁模式的问题是由于初始化对象时指令重排序锁导致的,而volatile关键字正好可以禁止指令重排序,因此,考虑将volatile关键字应用于单例模式中,便可完美解决双检锁的问题,让双检锁方案变得可行:

public class Singleton{   
  private volatile static Singleton instance;    //声明静态的单例对象的变量   
  private Singleton(){}    //私有构造方法    
     
  public static Singleton getInstance(){    //外部通过此方法可以获取对象     
    if(instance== null){      
        synchronized (Singleton.class) {   //保证了同一时间只能只能有一个对象访问此同步块         
            if(instance == null){       
                instance = new Singleton();           
        }      
      }   
    }     
    return instance;   //返回创建好的对象    
  }   
} 
综上, volatile修饰instance,使得在初始化instance时,保证按step1->3->2的顺序执行,不会出现单纯双检锁时出现的问题。