设计模式之单例模式

单例模式
    一个类只有一个实列,而且自行实例化并向整个系统提供这个实例。
    实现单例模式有以下几个关键点:
        1.构造函数不对外开放,一般为Private;
        2.通过一个惊呆方法或者枚举返回单例对象;
        3.确保单例类的对象有且只有一个,尤其是在多线程环境下;
        4.确保单例类对象在反序列化时不会重新构建对象。


1. 饿汉单例模式
    public class Singleton {
        
        private static final Singleton instance = new Singleton();

        private Singleton() {}

        public static Singleton getInstance() {
            return instance;
        }
    }

2.懒汉单例模式
    public class Singleton {

        private static Singleton instance;

        private Singleton() {}

        public static synchronized Singleton getInstance() {
            if (null == instance) {
                instance = new Singleton();
            }
            return instance;
        }
    }
        优点:单例只有在使用时才会被实列化,在一定程度上节约了资源。
        缺点:第一次加载时需要进行实列化,反应稍慢,最大问题时每次调用
            getInstance()都进行了同步,造成不必要的同步开销。这种模式一般不建议使用。

3.Double Check Lock(DCL) 实现单例

    public class Singleton {

        private volatile static Singleton instance;

        private Singleton() {
        }

        public static Singleton getInstance() {
            if (null == instance) {
                synchronized (Singleton.class) {
                    if (null == instance) {
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    }
    优点:能够在需要时才初始化单例,有能够包正线程安全,且店力对象初始化后调用 getInstance() 不进行同步锁。
    缺点: 第一次加载时反应稍慢,由于Java内存模型的原因偶尔会失败。在高并发环境下也有一定的缺陷,虽然发生概率比较小。

    为什么getInstance()方法中对 instance 进行了两次判空,而且还加了volatile关键字 ?
        假设线程A执行到 instance = new Singleton() 语句,这里看起来是一句代码,但实际上它并不是一个原子操作,这句代码最终会被编译成多条汇编指令,大致做了3件事情:
            1.给Singleton 的实例分配内存;
            2.调用Singleton()的构造函数,初始化成员字段;
            3.将instance 对象指向分配的内存空间(此时instance就不是nulll 了)。
            但是,由于Java编译器允许处理器乱序执行,以及JDK1.5之前 JVM( Java Memory Model,即 Java 内存模型)中 Cache、寄存器到主内存回写顺序的规定,上面的第二和第三的顺序是无法保证的。
            在JDK1.5之后,SUN 官方已经注意到了这个问题,调整了JVM,具体化 volatile 关键字,保证 instance 对象每次都是从主内存中读取。

4.静态内部类单例模式
    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 类,这种方式不仅能够线程安全,也能保证单例对象的唯一性,同时也延迟了单例的实例化。推荐使用这种单例模式实现

5.枚举单例模式
        public enum SingletonEnum {
            INSTANC;

        }
    枚举在Java中与普通类时一样的,不仅能够有字段,还能够有自己的方法。最终要的是默认枚举实例的创建是线程安全的,并且在任何情况下它都是一个单例。

        为什么这么说?在上述的几种单例模式实现中,在一个情况下它们会出现重新创建对象的情况,那就是反序列化,
            通过序列化可以将一个单例的实列对象写到磁盘,然后再读回来,从而有效的获得一个实例。即使构造方法是私有的,反序列化时依然可以通过特殊的途径去创建类的一个新的实例,相当于调用该类的构造函数。反序列化操作提供了一个很特别的钩子函数,类中具有一个私有的、被实例化的readResolve(),这个方法可以让开发人员控制对象的反序列化。例如,上述几个示例中如果要杜绝单例对象在被反序列化时重新生成对象,那么必须加入如下方法:
            private Object readResolve()throws ObjectStreamException {
            return INSTANC;
        }

6.容器实现单例模式
    public class SingletonManager {

        private static Map<String, Object> objectMap = new HashMap<>();

        private SingletonManager() {}

        public static void registerService(String key, Object instance) {
            if (!objectMap.containsKey(key)) {
                objectMap.put(key, instance);
            }
        }

        public static Object getService(String key) {
            return objectMap.get(key);
        }
    }

    在程序的初始,将多种单例类型注入到一个统一的管理类中,在使用时根据Key 获取对象对应类型的对象。这种方式使得我们可以管理多种类型的单例,并且在使用时可以通过统一的接口进行获取操作,降低了用户的使用成本,也对用户隐藏了具体实现,降低了耦合度。

    总结:不管以哪种方式实现单例模式,他们的核心都是将构造函数私有化,并且通过静态方法获取一个唯一的实例,在这个获取的过程必须保证线程安全、防止序列化导致重新生成实例对象等问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值