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

【参考文献】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的顺序执行,不会出现单纯双检锁时出现的问题。








            
阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页