透过DCL单例实现谈谈安全发布

1.不安全的发布

线程之间共享数据时,就是在“发布一个共享对象”与“另一个线程访问该对象”之间缺少一种Happens-Before关系(比如A、B两个线程,想要保证操作B的线程看到操作A的结果,那么A与B必须满足Happens-Before关系)时,就可能出现重排序问题。在没有充分同步的情况下,发布一个对象可能导致另一个线程看到一个只被部分构造的对象。

先来看个双重检查加锁(DCL)单例实现

/**
 * DCL单例
 * @author renhj
 *
 */
public class DoubleCheckedLocking {

	public static DoubleCheckedLocking instance;

	private DoubleCheckedLocking(){

	}
	public static DoubleCheckedLocking getInstance(){

		if(instance == null){

			synchronized(DoubleCheckedLocking.class){

				if(instance == null){

					instance = new DoubleCheckedLocking();
				}
			}
		}
		return instance;
	}

	public static void main(String[] args) {

		System.out.println(DoubleCheckedLocking.getInstance());
		System.out.println(DoubleCheckedLocking.getInstance());

	}

}

instance = new DoubleCheckedLocking();这一行创建对象并不是一个原子操作,分为以下三步:

1)Allocate memory       //给DoubleCheckedLocking分配内存

2)invoke constructor   //DoubleCheckedLocking构造器实例化

3)give reference       //instance引用指向分配的内存地址

由于Java编译器允许处理器乱序执行(out-of-order),上面的第二点和第三点的顺序是无法保证的,也就是说,执行顺序可能是1-2-3也可能是1-3-2,如果是后者,当前instance引用是一个部分构造的对象。

这种DCL实现方式很糟糕,首先检查是否在没有同步的情况下需要初始化,如果instance引用不为空,那么就直接使用它。否则就进行同步并检查Resource是否被初始化,从而保证只有一个线程对共享的DoubleCheckedLocking执行初始化。DCL的真正问题在于:没有同步的情况下读取一个共享对象时,可能发生的最糟糕的事情只是看到一个失效值(在这种情况下是个空值),此时DCL方法将通过在持有锁的情况下再次尝试来避免这种风险。然而实际情况远比这种糟糕,线程可能看到引用的当前值,但是对象的状态确是失效的(这种情况下是个部分构造的对象)

在Java5.0以后更高的版本中,如果把instance声明为volatile类型,对instance的写会强制将对缓存的修改操作立即写入主存,其他线程缓存中的instance都会设置为无效状态,从而确保共享内存中读到的instance是最新的,而且满足Happens_Before关系。

2.安全发布的常用模式

可变对象必须通过安全的方式来发布,要安全的发布一个对象,对象的引用以及对象的状态必须同时对其他线程可见,一个正确的构造对象可以通过以下方式来安全的发布:

1. 在静态初始化函数中初始化一个对象的引用。

2. 将对象的引用保存到一个volatile类型的域或者AtomicReferance对象中。

3. 将对象的引用保存到一个正确构造对象的final类型域中。

4. 将对象的引用保存到一个由锁保护的域中。

静态初始化器由JVM在类的初始化阶段执行,JVM内部存在着同步机制,因此通过这种方式初始化任何对象都可以被安全的发布,例如:public static DoubleCheckedLocking dcl = new DoubleCheckedLocking() ;volatile修饰的对象具有可见性,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值,从而确保对象的安全发布;final修饰的对象,那么该对象不可变,可以确保被安全的发布;锁确保可见性,对对象的写和读操作都上锁,能确保对象的安全发布。

附Happens-Before偏序关系一览:

1. 程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作

2. 锁定规则:一个unLock操作先行发生于后面对同一个锁额lock操作。这里必须强调的是同一个锁,这里“后面”是时间上的先后顺序。

3. volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作,这里“后面”是时间上的先后顺序。

4. 线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作。

5. 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过Thread.interrupted()方法检测到是否中断发生。

6. 线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行。

7. 对象终结规则:一个对象的初始化完成(构造函数执行结束)先行发生于他的finalize()方法的开始。

8. 传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C。

时间: 2024-10-14 12:22:42

透过DCL单例实现谈谈安全发布的相关文章

从一个简单的Java单例示例谈谈并发

一个简单的单例示例 单例模式可能是大家经常接触和使用的一个设计模式,你可能会这么写 public class UnsafeLazyInitiallization { private static UnsafeLazyInitiallization instance; private UnsafeLazyInitiallization() { } public static UnsafeLazyInitiallization getInstance(){ if(instance==null){ /

从一个简单的Java单例示例谈谈并发 JMM JUC

原文: http://www.open-open.com/lib/view/open1462871898428.html 一个简单的单例示例 单例模式可能是大家经常接触和使用的一个设计模式,你可能会这么写 public class UnsafeLazyInitiallization { private static UnsafeLazyInitiallization instance; private UnsafeLazyInitiallization() { } public static U

一个简单的Java单例示例谈谈并发

一个简单的单例示例 单例模式可能是大家经常接触和使用的一个设计模式,你可能会这么写 public class UnsafeLazyInitiallization { private static UnsafeLazyInitiallization instance; private UnsafeLazyInitiallization() { } public static UnsafeLazyInitiallization getInstance(){ if(instance==null){ /

Java设计模式:Singleton(单例)模式

概念定义 Singleton(单例)模式是指在程序运行期间, 某些类只实例化一次,创建一个全局唯一对象.因此,单例类只能有一个实例,且必须自己创建自己的这个唯一实例,并对外提供访问该实例的方式. 单例模式主要是为了避免创建多个实例造成的资源浪费,以及多个实例多次调用容易导致结果出现不一致等问题.例如,一个系统只能有一个窗口管理器或文件系统,一个程序只需要一份全局配置信息. 应用场景 资源共享的情况下,避免由于资源操作时导致的性能或损耗等.如缓存.日志对象.应用配置. 控制资源的情况下,方便资源之

Java-----关于单例设计模式

1. 单例模式DCL写法 单例设计模式中,有一种双重检查锁的写法, 也就是所谓的懒汉式 class Single{ private static Single sSingle; private Single() {} public static Single getInstance() { if(sSingle == null) { synchronized(Single.class) { if(sSingle == null) { sSingle = new Single(); } } } r

DCL的单例一定是线程安全的吗

读了本文,你会知道,为什么不加volatile关键字的单例模式不是线程安全的 有经验的开发者都知道双重锁定检查(DCL,Double Check Lock)的单例是最优秀的,如下文所示: 1 public class Singleton { 2 private static Singleton instance = null; 3 public static Singleton getInstance() { 4 if(null == instance) { // 第一次检查 5 synchro

深入谈谈Java最简单的单例设计模式

单例设计模式是23种设计模式里面最简单的,但是要彻底理解单例,还是需要下一点功夫的. 单例一般会分为饿汉模式和懒汉模式 饿汉模式: 1 public class Singleton 2 { 3 private static Singleton singleton = new Singleton(); 4 5 public static Singleton getInstance() 6 { 7 return singleton; 8 } 9 } 但是在一些系统应用环境中,这个单例对象可能比较大,

单例实现备忘

1. 常见方式 保证线程安全条件下有三种方式: 提前初始化:类加载时就创建一个对象, 延迟初始化 DCL:双重检查,JDK5.0后单例实例加volatile修饰 占位类:推迟占位类的初始化 提前初始化可能影响程序启动时间以及不必要高开销操作较少采用 2. DCL public class DoubleCheckLocking {     private static Resource resource;     public static Resource getInstance(){     

Swift中编写单例的正确方式

Swift中编写单例的正确方式 2015-12-07 10:23 编辑: yunpeng.hu 分类:Swift 来源:CocoaChina翻译活动 14 10647 Objective-CSwift单例 招聘信息: Cocos2d-x 工程师 cocos2dx手游客户端主程 wp开发 iOS开发工程师 iOS软件工程师 iOS研发工程师 iOS讲师 iOS开发工程师 iOS高级开发工程师 iOS 高级软件工程师 iOS高级开发工程师 本文由CocoaChina译者leon(社区ID)翻译自kr