`
ayagen
  • 浏览: 12701 次
  • 性别: Icon_minigender_1
  • 来自: 广州
文章分类
社区版块
存档分类
最新评论

多线程下的单例模式:双检锁,以及volatile的使用(HB规则)

阅读更多
参考几位高人的文章,然后结合自己的理解,在此做一个小小的总结:
http://www.iteye.com/topic/537563
http://www.iteye.com/topic/260515
http://www.iteye.com/topic/344876

在多线程环境下的单例模式:
1. 使用(Double-Checking-Lock)双检锁:
public class Singleleton {
   private Singleleton() {}
   private static Singleleton instance = null;
   public static Singleleton getInstance() {
      if(instance == null){ 
         synchronize(Singleleton.class){
            if(instance == null){
               instance = new Singleton();
            }
         }
      }
      return instance;
   }
}

“双重检查锁定背后的理论是完美的。不幸地是,现实完全不同。双重检查锁定的问题是:并不能保证它会在单处理器或多处理器计算机上顺利运行。双重检查锁定失败的问题并不归咎于 JVM 中的实现 bug,而是归咎于 Java 平台内存模型。内存模型允许所谓的“无序写入”,这也是这些习语失败的一个主要原因。”
(http://www.ibm.com/developerworks/cn/java/j-dcl.html#2)

DCL被破坏的原因在于:
两个线程可能最终得到的instance引用是一样的,但是却有可能访问到instance的成员变量的不正确值(因为instance的引用值在其真正的构造函数执行之前被返回,或者称之为:被泄露)
针对instance =new Singleton(),其对应的伪代码可能为:
1. mem = allocate();//Allocate memory for Singleton object.
2. instance = mem;  //Note that instance is now non-null, but has not been initialized.
3. ctorSingleton(instance);  //Invoke constructor for Singleton passing instance.

其中第二句和第三句的执行顺序是不确定的(因为有reorder的存在),而这点正是引发问题的关键.

要想避免reorder,可以使用关键字volatile(前提是JVM很好地实现了volatile的顺序一致性):
被volatile修饰的变量A,系统不允许对A的写操作和之前的读写操作(这里的"读写"包括任何变量的读写,不只是针对A)进行优化重排执行(reorder),同时,不会对读操作和之后的读写操作进行优化重排执行:
<Java Concurrency in Practise>: the system will not allow a write of a volatile to be reordered with respect to any previous read or write, and a read of a volatile cannot be reordered with respect to any following read or write.

这样,volatile保证了3肯定先执行,然后再执行2.

使用Happen-Before(HB)规则可以更好的理解volatile的作用:
int volatile count; 
int number;
....
1. int a = count;
2. int b = number;

因为count被声明为volatile,语句1在读取count的时候应该读取最近一次对count的写入值,因此最近一次针对count的写操作W1(count)和当前的读操作R1(count)两者之间的顺序是不可以被优化调整的,即必有W1(count) -HB-> R1(count)
如果在代码中对number的写总是发生在对count写之前,即有关系W1(number) -HB-> W1(count) -HB-> R1(count),再根据语句顺序有R1(count)-HB->R1(number)
根据HB的传递性,我们可以得到W1(number) -HB-> R1(number),也即:在取number的时候应该读取最近一次对number的写入.
这里虽然number并未被声明为volatile,但由于语句1的存在,保证了number的可见性.即使变量a在下面的语境中从未被引用,编译器也不会在优化时把语句1去掉(因为volatile的存在)

如果想了解更具体的语境,可以参考CurrentHashMap中关于count和modCount的使用,这里有一篇文章,讲得很好:
http://www.iteye.com/topic/344876

2. 使用static来实现
public class Singleleton {
  private static class SingleletonHolder{
    private static Singleleton instance = new Singleleton();
  }
  public static Singleleton getInstance(){
    return SingleletonHolder.instance ;
  }
}

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics