美文网首页
单例模式与双重检测(DCL,Double Check Lock)

单例模式与双重检测(DCL,Double Check Lock)

作者: 凉风拂面秋挽月 | 来源:发表于2020-03-16 16:55 被阅读0次

    单例模式可以使得一个类只有一个对象实例,能够减少频繁创建对象的时间和空间开销。
    意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
    主要解决:一个全局使用的类频繁地创建与销毁。
    何时使用:当您想控制实例数目,节省系统资源的时候。
    如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
    关键代码:构造函数是私有的。

    饿汉单例

    在类初始化后即创建好,等待被调用,多线程下安全可靠,无副作用

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

    懒汉模式(懒加载)

    1.单线程下的懒汉单例

    只有被用到的时候才会给单例创建对象。

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

    问题:在多线程状态下,如果A,B同时执行到if条件判断 A判断singleton为空准备执行,然后让出了CPU时间片; B也判断singleton为空,接着执行,此时创建了一个实例对象;A获取了CPU时间片后接着执行,也创建了实例对象,这就导致多个单例对象的情况。
    解决方案:加同步。

    2同步保证线程安全的单例

    public class Singleton2 {
        private static Singleton2 singleton2;
        private Singleton2() {
        }
        public static synchronized Singleton2 getInstance() {
            if(singleton2==null) {
                return new Singleton2();//1
            }
            return singleton2;
        }
    }
    

    新问题:虽然解决了多线程并发的问题,但是却带来了效率问题: 我们的目的是只创建一个实例,即//1处代码只会执行一次,也正是这个地方才需要同步。但现在的写法是锁住了整个方法,同一时间只能有一个线程获得这个单例对象,效率低下。

    解决方案:降低锁的粒度,只需要锁住1即可

    同步代码块降低锁粒度

    public class Singleton3 {
        private static Singleton3 singleton3;
        private Singleton3() {
        }
        public static Singleton3 getInstance() {
            if(singleton3==null) {
                synchronized (Singleton3.class) {
                    return new Singleton3();//1
                }           
            }
            return singleton3;
        }
    }
    

    新问题:这样会带来与第一种一样的问题,即多个线程同时执行到条件判断语句时,会创建多个实例。 A,B同时判断为空,同时执行if,与第一种懒汉的区别是这里A获得锁先创建对象,B后获得锁后创建对象。

    解决方案: 问题在于当一个线程创建一个实例之后,singleton就不再为空了,
    但是后续的线程并没有做第二次非空检查。那么很明显,在同步代码块中应该再次做检查,也就是所谓的双重检测。

    懒汉的双重检查

    public class Singleton4 {
        private static Singleton4 singleton4;
        private Singleton4() {
        }
        public static Singleton4 getInstance() {
            if(singleton4==null) {//一重检测
                synchronized (Singleton4.class) {//加锁
                    if(singleton4==null) {//二重检测
                        return new Singleton4();//1
                    }               
                }           
            }
            return singleton4;
        }
    }
    

    单例在双重加载模型下在逻辑上已经是线程安全的了,但是依然可能出现问题,即空指针异常。

    问题:一般出在单例的构造方法里执行语句过多。原因还是出在1上,由于jvm会自动对程序优化,会发生指令重排序的情况,即jvm的执行顺序可能与程序的顺序不相同。
    无序写入可能会造成顺序的颠倒,即内存分配、返回对象引用、初始化的顺序,这种情况下对应到//1就是singleton已经不是null,而是指向了堆上的一个对象,这时这个对象还是个空壳子,啥都没有。但是此时B线程却看到堆的引用已经出来了,就直接拿来用,结果出现空指针异常。

    解决方案:volatile可解决指令重排序问题。(为什么能解决推荐看深入jvm虚拟机,简单说下就是在字节码执行时有内存屏障,重排序中顺序后面的语句不能越过屏障排序)

    懒汉最终板volatile+双重检测

    public class Singleton5 {
        private volatile static Singleton5 singleton5;
        private Singleton5() {
        }
        public static Singleton5 getInstance() {
            if(singleton5==null) {//一重检测
                synchronized (Singleton5.class) {//加锁
                    if(singleton5==null) {//二重检测
                        return new Singleton5();
                    }               
                }           
            }
            return singleton5;
        }
    }
    

    单例模式

    相关文章

      网友评论

          本文标题:单例模式与双重检测(DCL,Double Check Lock)

          本文链接:https://www.haomeiwen.com/subject/vxagehtx.html