DCL单例模式,如何解决DCL问题

DCL单例模式,如何解决DCL问题何为 DCL DCL 即 DoubleCheckL 双重检查锁定 下面从几个单例模式来讲解懒汉式 publicvoidSi privatestati privateSingl publicstatic if

何为DCL,DCL即Double Check Lock,双重检查锁定。下面从几个单例模式来讲解

懒汉式

public void Singleton{ private static Singleton singleton; private Singleton(){} public static Singleton getInstance(){ if(singleton==null){ singleton=new Singleton(); } return singleton; } }

这种方法在单线程下是可取的,但是在并发也就是在多线程的情况下是不可取的,因为其无法保证线程安全,优化如下:

public void Singleton{ private static Singleton singleton; private Singleton(){} public synchronized static Singleton getInstance(){ if(singleton==null){ singleton=new Singleton(); } return singleton; } }

优化非常简单,在getInstance方法上加上了synchronized同步,尽管jdk6以后对synchronized做了优化,但还是会效率较低的,性能下降。那该如何解决这个问题?于是有人就想到了双重检查DCL

public void Singleton{ private static Singleton singleton; private Singleton(){} public static Singleton getInstance(){ if(singleton==null){ synchronized(Singleton.class){ if(singleton==null) singleton=new Singleton(); } } return singleton; } }

这个代码看起来perfect:

  1. 如果检查第一一个singleton不为null,则不需要执行加锁动作,极大的提高了性能
  2. 如果第一个singleton为null,即使有多个线程同时判断,但是由于synchronized的存在,只有一个线程能创建对象
  3. 当第一个获取锁的线程创建完成singleton对象后,其他的在第二次判断singleton一定不会为null,则直接返回已经创建好的singleton对象

DCL看起来非常完美,但其实这个是不正确的。逻辑没问题,分析也没问题?但为何是不正确的?不妨我们先回顾一下创建对象的过程

  1. 为对象分配内存空间
  2. 初始化对象
  3. 将内存空间的地址赋值给对应的引用

但由于jvm编译器的优化产生的重排序缘故,步骤2、3可能会发生重排序:

  1. 为对象分配内存空间
  2. 将内存空间的地址赋值给对应的引用
  3. 初始化对象

如果2、3发生了重排序就会导致第二个判断会出错,singleton != null,但是它其实仅仅只是一个地址而已,此时对象还没有被初始化,所以return的singleton对象是一个没有被初始化的对象

知道问题的原因,那么我们就可以解决?

不允许重排序

重排序不让其他线程看到

解决方法

利用volatile的特性即可阻止重排序和可见性

public class Singleton { //通过volatile关键字来确保安全 private volatile static Singleton singleton; private Singleton(){} public static Singleton getInstance(){ if(singleton == null){ synchronized (Singleton.class){ if(singleton == null){ singleton = new Singleton(); } } } return singleton; } }

类初始化的解决方案

public class Singleton { private static class SingletonHolder{ public static Singleton singleton = new Singleton(); } public static Singleton getInstance(){ return SingletonHolder.singleton; } }

该解决方案的根本就在于:利用classloder的机制来保证初始化instance时只有一个线程。JVM在类初始化阶段会获取一个锁,这个锁可以同步多个线程对同一个类的初始化。

Java语言规定,对于每一个类或者接口C,都有一个唯一的初始化锁LC与之相对应。从C到LC的映射,由JVM的具体实现去自由实现。JVM在类初始化阶段期间会获取这个初始化锁,并且每一个线程至少获取一次锁来确保这个类已经被初始化过了。

 

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/220015.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月17日 下午9:26
下一篇 2026年3月17日 下午9:26


相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号