美文网首页
单例模式

单例模式

作者: 壮少Bryant | 来源:发表于2019-07-10 15:31 被阅读0次

    使用场景

    实际的开发中,为了避免创建多个对象消耗过多的资源,或者某个类的对象只能有一个,所以就需要使用单例模式来确保某个类只能对外提供一个对象。

    特点

    • 类的构造函数一般用private修饰,不对外公开
    • 一般通过一个静态方法返回单例对象
    • 必须保证线程安全,即在多线程场景下能确保只有一个单例对象

    1 懒汉加载

    1.1 简单粗暴有缺点

    public class Singleton {  
        private Singleton() {}  
        private static Singleton single = null;  
        //缺点:每一次都要 synchronized 同步,造成不必要的开销
        public static synchronized Singleton getInstance() {  
             if (single == null) {    
                 single = new Singleton();  
             }    
            return single;  
        }  
    }  
    

    1.2 改进:双重检查锁定

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

    第一重判断:改进了不用每次都同步的缺点 ,提升效率
    第二重判断:如果两个线程都进入了第一重判断,由于同步,只能进来一个。A进入了创建了实例,出来后B还可以进入,如果没有第二次判断,就会生成多个instance

    另外可能失效:java的指令重排序。single = new Singleton();可以分成多条汇编指令
    (1)、给Singleton实例分配内存(2)、调用构造函数,初始化成员(3)、将instance对象指向分配内存的空间,注意此时instance就不为null了
    可以是123,也可以是132,如果是132,A执行3的时候,2未执行,此时instance就不为空,B就把instance取走了,这就是失效的原因。

    解决办法:private volatile static Singleton single = null

    即添加volatile修饰符,这样就可以保证instance每次都从主内存读取,避免了上边的问题,但会略影响性能。这种单例模式也是在第一次执行getInstance()时创建单例,但第一次反映稍慢。

    这种方式目前使用的较多。

    1.3 推荐使用:静态内部类单例模式

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

    这种方式只有在的第一次调用getInstance()方法时,虚拟机才会加载SingletonHolder类,并初始化instance实例,即保证了线程同步,也能保证单例的唯一性,相对双重检查锁定单例模式简单了许多,推荐使用这种方式来实现单例模式。

    2 饿汉单例模式

    //饿汉式单例类.在类初始化时,已经自行实例化,以后不再改变,所以天生是线程安全的   
    public class Singleton {  
        private Singleton() {}  
        private static final Singleton single = new Singleton();  
        //静态工厂方法   
        public static Singleton getInstance() {  
            return single;  
        }  
    }  
    

    饿汉式在类创建的同时就实例化一个静态对象出来,不管之后会不会使用这个单例,都会占据一定的内存,但是相应的,在第一次调用时速度也会更快,因为其资源已经初始化完成。

    而懒汉式顾名思义,会延迟加载,在第一次使用该单例的时候才会实例化对象出来,第一次调用时要做初始化,如果要做的工作比较多,性能上会有些延迟,之后就和饿汉式一样了。

    3 容器单例模式

    public class SingletonManager {
        private static Map<String, Object> instanceMap = new HashMap<>();
    
        private SingletonManager() {
        }
    
        public static void addInstance(String key, Object instance) {
            if (!instanceMap.containsKey(key)) {
                instanceMap.put(key, instance);
            }
        }
    
        public Object getInstance(String key) {
            return instanceMap.get(key);
        }
    }
    

    采用Map集合管理对象的实例,保证实例的唯一性,这种方式多用于管理多种类的实例场景,同时你的类并不一定需要实现单例机制,因为SingletonManager可以解决这个问题。你只需在初始化时创建对应类的实例并调用addInstance(String key, Object instance)来进行保存,使用时调用getInstance(String key),即可根据key得到对应类的实例。


    声明:此文章为本人学习笔记

    如果您觉得有用,欢迎关注我的公众号,我会不定期发布自己的学习笔记、资料、以及感悟,欢迎留言,与大家一起探索AI之路。

    AI探索之路

    相关文章

      网友评论

          本文标题:单例模式

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