美文网首页Java设计模式
漫画设计模式:每周一篇设计模式之单例模式

漫画设计模式:每周一篇设计模式之单例模式

作者: 天才少年_曹 | 来源:发表于2019-12-10 12:34 被阅读0次






    【首先不管何种形式实现单例模式,构造方法一定是私有的,这是大前提。】

    饿汉模式
    饿汉模式中的类实例是当类被加载时就被初始化出来的,所以在应用初始化时,会占用不必要的内存。同时,由于该实例在类被加载的时候就创建出来了,所以他是线程安全的。因为类的初始化是由ClassLoader完成的,利用了ClassLoader的线程安全机制,ClassLoader的loadClass方法在加载类的时候使用了synchronized关键字实现线程同步。

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

    instance对象在Singleton类被加载的时候,被实例化出来,他的实例化跟着类加载一起进行,很简单,保持了唯一性。


    通过静态内部类来实现的饿汉模式
    public class Singleton {  
        private static class SingletonHolder {  
        private static final Singleton INSTANCE = new Singleton();  
        }  
        private Singleton (){}  
        public static final Singleton getInstance() {  
        return SingletonHolder.INSTANCE;  
        }  
    } 
    

    饿汉模式在类被加载时,就创建出对象,而通过静态内部类的方式,Singleton对象被加载时,INSTANCE没有被初始化,SingletonHolder类不会被加载,只有在调用getInstance()方法时,才会加载SingletonHolder类,实例化INSTANCE对象。由于类的初始化是由ClassLoader完成的,利用了ClassLoader的线程安全机制,所以通过静态内部类来实现的饿汉模式既不过早消耗资源,又能保证线程安全。





    懒汉式
    懒汉式顾名思义就是不会提前做准备,在使用的时候,才会实例化对象。
    public class Singleton {  
        private static Singleton instance;  
        private Singleton (){}  
    
        public static Singleton getInstance() {  
        if (instance == null) {  
            instance = new Singleton();  
        }  
        return instance;  
        }  
    } 
    

    上面的代码很简单,通过 if (instance == null) 判断是否已经存在instance对象,存在的话,直接返回,不存在,则实例出instance对象。








    1)粗暴式加锁

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

    可见在getInstance()方法上增加synchronized,通过锁就可以实现线程安全,但是这种形式加锁的范围是整个初始化方法,效率很低,因为加锁的目的是保证第一次创建对象是同步的,不是第一次创建对象的情况,没有必要进行同步,可以直接返回instance。当多个线程调用getInstance()方法时,全部在等第一个线程释放锁,效率不高。

    2)双重锁

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

    相信双重锁对很多人来说并不陌生,尤其是经常面试的同学,单例双重锁是面试经常被问到的。
    首先,通过使用同步代码块的方式减小了锁的范围,提高了效率,同时引入volatile阻止对象初始化的指令重排,实现多线程同步。



    当执行singleton = new Singleton(); 语句时,正常会分下面三个步骤:
    1)分配内存
    2)初始化对象
    3)将singleton指向分配的内存地址
    可以看到,实例化对象并不是原子操作,并且编译器可能会指令重排,比如以上步骤被重排为下面的步骤:
    1)分配内存
    2)将singleton指向分配的内存地址
    3)初始化对象
    这样的话,如果线程A先分配内存,再singleton指向分配的内存地址 ,最后初始化对象时可能会出现如下情况:当线程A还没有执行3)初始化对象时,线程2执行到 if (singleton == null) 语句,因为线程A已经把singleton指向了内存地址,所以if (singleton == null) 语句返回false,getSingleton()方法则直接把未初始化的singleton对象返回回去,这个时候,线程B用到singleton对象时,就会出现空指针。我们通过volatile来阻止指令重排,从而避免上面问题的发生。



    如有错误欢迎指出来,一起学习。


    相关文章

      网友评论

        本文标题:漫画设计模式:每周一篇设计模式之单例模式

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