Java中的volatile在使用双层检查实现单例模式的解读

 

1.前言

因为今天在想到这个问题的时候脑子不是很清楚,就想查一下网上的资料,结果发现一个个写的囫囵吞枣。后来突然想起来了,于是打算记录下来。

注意此种方法只针对JDK1.5及以上,之前好像是volatile的关键字设计有问题?

2.双层检查实现单例模式的由来

最开始只有一层检查,

【失败的设计】
public Resource getResource() {
 if (resource == null) { 
    resource = new Resource(); 
 }
 return resource;
}
大家应该都可以看出,在多线程情况下,会出现多个new Resource()的动作
 
这时,有人就提出了一个解决方案
【成功但是糟糕的设计】
public synchronized Resource getResource() {
 if (resource == null) { 
    resource = new Resource(); 
 }
 return resource;
}
可以看到,他给getResource方法加上了synchronized关键字,这足以保证只有一个线程会执行new Resource操作。
这个设计可以满足需求,但是有一个不太好的地方就是每次执行getResource方法的时候都会有加锁去锁的开销,而这中加锁只有在初始化前才是必须的。
 
所以有有人说,那可不可以这样:1
public Resource getResource() {
 if (resource == null) { 
  synchronized(this){ 
   if (resource==null) {
    resource = new Resource(); 
   }  
  
 }
 return resource;
}
稍作解释,他的想法是进行两次判断,如果第一次判断不为null,也就是resource已经初始化,那么久不会进去执行加锁去锁的操作了。
但这个方案可不可能需要看resource有没有加volatile关键字。

如果加了volatile关键字,这个方法是有效的。如果没有,这个方法是有一定多线程风险的。

现在进入本文的重点,此处为什么不加volatile有风险。

首先声明:

此处利用了volatile的一个关键字特性:防止局部指令重排序

简单的概括就是,

volatile禁止指令重排序的一些规则:2
  1.当第二个操作是voaltile写时,无论第一个操作是什么,都不能进行重排序
  2.当第一个操作是volatile读时,不管第二个操作是什么,都不能进行重排序
  3.当第一个操作是volatile写时,第二个操作是volatile读时,不能进行重排序

下面来看new Resource()这个操作,

它可以分解成三个操作,

【伪代码】3

1.memory = allocate()

2.createInstance(memory)

3.resource = memory

其中1,2指令有数据依赖,所以不会被重排序。而2,3指令没有数据依赖,如果没有volatile关键字,可能会被重排序。

那么假如2,3执行顺序进行了调换。

那么就有可能发生,假设A,B线程都即将执行getResource操作,目前在A线程,

首先A线程第一次判断resource是否为null,结果为null,那么加锁进入执行创建对象的这三步。

假设执行了上述的1,3后,发生了调度,B开始执行。

此时它面临的状态是,判断resource是否为null,结果不为null.因为刚才A执行了3已经给resource赋值了。

那么B会认为resource已经初始化完成,它可能要对这个对象进行一些操作,但是事实上A还没有执行2操作,resource对象还没有初始化完成。

这样运行下去可能会产生异常,风险由此产生。

不过加了volatile后,在执行对resource写之前,volatile关键字保证了1,2,3操作不会重排序(需要考证),这样就保证了resource要么为null,要么就是一个完整的resource对象。

顺便补充一下,加了volatile之后,即使在执行1,2,3的时候发生了调度,因为锁在A线程手里,所以B线程没有办法拿到锁进行初始化。所以即使调度时候resource为null,也不会发生多次初始化的情形。

 

如果需要对volatile有更多的了解,

可以访问官方Java Language Specification

关于volatile: http://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#d5e12277

关于Java内存模型: http://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.4

参考文档:

1.http://www.jb51.net/article/80201.htm

2.https://blog.csdn.net/hqq2023623/article/details/51013468

3.http://blog.csdn.net/xiakepan/article/details/52444565

posted @ 2017-07-02 23:16  lightverse  阅读(1867)  评论(0编辑  收藏  举报