`
asticx
  • 浏览: 5889 次
社区版块
存档分类
最新评论

DCL,volatile,Final

阅读更多

Double check lock模式的典型代码:

 

 

public class Singleton {

    private static Singleton instance = null;

    private Singleton() {
    }

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }

        return instance;   
    }
}

 

 

理论上来将,DCL模式能够以优异的性能解决单例模式在多线程下的并发问题,但是实际情况下,因为编译器会采用代码重排(instruction reorder)的优化手段,从而导致偶尔出现返回一个未完全初始化(内存已经分配,reference对象已经创建并且指向创建的内存地址,但是构造函数还没有开始执行或者未完全执行完毕)的Singleton实例并引起程序异常。

 

为了解决这个问题,JDK1.5以及之后的版本,可以通过volatile以及final关键字来修正DCL中的问题:

 

volatile比较简单,只需要在instance变量定义前加上volatile关键字即可,由于使用volatile关键字会禁止JVM的代码重排,因此可以解决DCL中存在的问题。

 

使用final关键字稍微有些地方需要注意,先看代码:

 

 

public class FinalWrapper<T> {
    public final T value;
    public FinalWrapper(T value) { 
        this.value = value; 
    }
}
 
public class Foo {
   private FinalWrapper<Helper> helperWrapper = null;
 
   public Helper getHelper() {
      FinalWrapper<Helper> wrapper = helperWrapper;
 
      if (wrapper == null) {
          synchronized(this) {
              if (helperWrapper == null) {
                  helperWrapper = new FinalWrapper<Helper>(new Helper());
              }
              wrapper = helperWrapper;
          }
      }
      return wrapper.value;
   }
}

 

首先实例变量value被声明为了final,但是比较特别的一点是在Foo.getHelper()方法中,首先定义了wrapper这个局部变量,并将helperWrapper赋给了它,然后判断wrapper是否为空再进行后续的处理。必须使用wrapper的原因是因为做为局部变量,wrapper不存在线程不安全的问题,如果直接使用helperWrapper,因为之前代码重排的原因,仍然有可能得到一个不完全初始化的对象实例。

 

再回到final关键字的语义,在JDK1.5以及之后的版本中,编译器能够保证被final关键字定义的属性在对象实例化中能够被正确的初始化(非final对象因为代码重排的原因可能不能满足这个条件),因此在最后一段程序中,wrapper.value永远能够被正确的初始化。

 

补充1:DCL是在Singleton模式中最容易被使用的,目的是为了解决Singleton在多线程下的线程安全问题。其实为了解决这个问题,除去上述方法之外,还有一些比较直接的方式,例如直接在声明Singleton instance时就将它初始化(无法做到延时加载)或者在getInstance()方法上加入synchronized关键字(性能问题),比较有趣的是下面这种通过内部类来实现延时加载(原理是利用了内部类直到被实际引用时才会加载的机制):

 

 

class Foo {
    private static class HelperHolder {
       public static Helper helper = new Helper();
    }
 
    public static Helper getHelper() {
        return HelperHolder.helper;
    }
}
 

补充2:关于volatile关键字,volatile变量保证在各个工作线程内存之间的一致性,但是不代表任何情况下基于volatile变量的操作都是线程安全的(在操作依赖于当前变量值并且会修改它的情况下,因此我们仍然需要使用DCL来保证单例的唯一性)。同时volatile变量保证了代码顺序的不变性,但是在和其他非volatile变量一起使用时,仍然会触发编译器的代码重排。

分享到:
评论

相关推荐

    单例模式(饿汉模式、懒汉模式、DCL单例模式、枚举)

    private static final Singleton INSTANCE = new Singleton(); private Singleton() {} public static Singleton getInstance() { return INSTANCE; } } ``` 2. **懒汉模式**: 懒汉模式是在首次调用 `...

    通过实例程序验证与优化谈谈网上很多对于Java DCL的一些误解以及为何要理解Java内存模型.doc

    为了确保有序性,我们需要使用`synchronized`关键字或者`volatile`配合`final`字段来构造线程安全的单例。 在DCL模式中,`volatile`关键字的第一次检查可以避免不必要的同步,而第二次检查加上`synchronized`块确保...

    volatile学习手册

    在实际编程中,volatile常用于单例模式的双重检查锁定(DCL)、发布静态初始化字段、线程中断标志等场景,以保证线程间的数据同步和正确性。 总结,volatile是Java并发编程中不可或缺的一部分,它提供了一种相对轻...

    多线程训练营资料2.xls.md

    #### 2.1 DCL 单例是否需要加 volatile? **双重检查锁定(Double-Checked Locking, DCL)**是一种用于创建线程安全的单例模式的方法。然而,在Java中如果没有正确使用`volatile`,则可能导致DCL单例出现异常。这是...

    设计模式——单实例模式、单件模式——Java编写

    在这个DCL实现中,volatile关键字确保了多线程环境下对instance的可见性和有序性,防止出现线程间的脏读问题。这里的关键在于两次检查instance是否为null,第一次是为了避免不必要的同步,第二次是在同步区域内创建...

    Java模式设计之单例模式[借鉴].pdf

    正确的DCL实现需要使用volatile关键字: ```java public class DoubleCheckSingleton { private volatile static DoubleCheckSingleton instance; private DoubleCheckSingleton() {} public static ...

    JAVA单例模式(三种)

    **正文** Java单例模式是一种常用的软件设计模式...需要注意的是,Java 5之后的版本,由于JVM对volatile的优化,DCL式已经能够很好地解决线程安全问题。在Java的并发编程中,正确理解和运用单例模式是非常重要的技能。

    单例设计模式源码和案例解析

    它在instance为null时才创建实例,并通过volatile关键字保证了多线程环境下的可见性和有序性。 ```java public class Singleton { private volatile static Singleton instance; private Singleton() {} public ...

    尚硅谷大厂面试题第二季周阳主讲整理笔记

    它要求可见性(线程看到其他线程修改过的变量值)、有序性(限制指令重排序),并定义了锁、volatile、final规则来保证这些特性。 【单例模式】 单例模式是一种设计模式,保证一个类只有一个实例。在Java中,DCL...

    java-单例模式几种写法

    DCL提高了性能,但涉及volatile关键字;静态内部类利用类加载机制,避免同步;枚举方式最简洁,且线程安全。 在实际开发中,根据项目需求和性能考虑,可以选择适合的单例模式实现。例如,如果资源消耗不是问题,...

    Java Singleton 实用教程(附源码)

    在Java中实现单例模式有多种方法,包括懒汉式、饿汉式、双重检查锁定(DCL)、静态内部类和枚举。每种方法都有其优缺点,适用于不同的场景。 1. **懒汉式**: 懒汉式单例的特点是延迟初始化,即只有当第一次需要时...

    单例模式SingletonMode

    在Java中实现单例模式有多种方法,包括懒汉式、饿汉式、双重检查锁定(DCL)以及枚举方式。下面我们将详细讨论这些实现方式及其优缺点。 1. **懒汉式**: 懒汉式单例的特点是在类第一次被请求时才进行实例化,延迟了...

    线程安全的单例模式

    双重检查锁定(Double-Checked Locking, DCL)技术在懒汉式的同步基础上做了优化,只在第一次实例化时进行同步操作,避免了每次调用都同步带来的性能损耗。需要注意的是,这里使用了 `volatile` 关键字来确保可见性...

    研磨设计模式之单例模式

    DCL模式通过`volatile`关键字确保了多线程环境下的可见性和有序性,同时避免了不必要的同步开销。 设计模式的目的是提高代码的可读性、可维护性和复用性。单例模式在许多场景下非常有用,如数据库连接池、缓存管理...

    设计模式--单例模式java例子

    在Java中实现单例模式有多种方法,包括懒汉式、饿汉式、双重检查锁定(DCL)、静态内部类以及枚举类型等。每种方式都有其优缺点,适用场景也不同。 1. **懒汉式**:延迟初始化,只有在第一次调用getInstance()方法...

    高并发常见面试题(深入底层).docx

    例如,在单例模式的实现中,通常采用双重检查锁定(Double-Checked Locking, DCL)模式,但如果不恰当地使用`volatile`关键字,可能会导致线程安全问题。具体来说,如果`Singleton`对象在尚未完全构造完成时被另一个...

    java单例模式代码实例

    利用类加载机制保证线程安全,延迟加载,同时避免了DCL的volatile问题。 ```java public class Singleton { private Singleton() {} private static class SingletonHolder { private static final Singleton ...

    单例模式详解

    private volatile static Singleton instance; private Singleton() {} public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { ...

    singleton-demo.zip

    private volatile static Singleton instance; private Singleton() {} public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { ...

Global site tag (gtag.js) - Google Analytics