常见的几种单例模式

    |     2018年3月21日   |   设计模式   |     0 条评论   |    1032

单例模式是常用设计模式,核心结构里只包含一个被称为单例的特殊类:一个类只有一个实例,也就是只有一个对象实例。

系统里有些类必须唯一。例如可以有多个打印任务,但同时只能有一个在工作;售票时一共 100 张票、多个窗口同时卖,余量必须是单例,否则会超售。窗口对象如果不唯一化,会弹出多个窗口;内容相同却重复创建,就是浪费资源。

一、应用场景

来源:《大话设计模式》。需求:在前端创建工具箱窗口,要么不出现,出现也只出现一个。

遇到的问题:每次点击菜单都会重复创建「工具箱」窗口。解决方案一:用 if,每次创建前先判断是否为 null,为 null 再 new。如果 5 个地方都要实例出工具箱,这个小 bug 要改 5 处,代码重复、利用率低。

解决方案二:用单例模式,保证一个类只有一个实例,并提供一个访问它的全局访问点。

下面几种写法会发现:所有单例都用静态方法创建,所以单例对象存在内存的静态共享区。(可参考:https://zhidao.baidu.com/question/2206072272164938188.html)

二、懒汉式与饿汉式

  • 懒汉式:类加载时不初始化。
  • 饿汉式:类加载时就完成初始化,类加载比较慢,但获取对象的速度快。
写法 类型 要点
第一种 懒汉,线程不安全 lazy loading 明显,多线程不能正常工作
第二种 懒汉,线程安全 getInstance 加 synchronized,效率低
第三种 饿汉 类装载时实例化,无 lazy loading
第四种 饿汉变种 静态块初始化,效果同第三种
第五种 静态内部类 调用 getInstance 才装载内部类
第六种 枚举 Effective Java 提倡,防反序列化
第七种 双重校验锁 JDK 1.5 之后才可靠

三、七种写法

第一种(懒汉,线程不安全)

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

这种写法 lazy loading 很明显,但是致命的是在多线程不能正常工作。

第二种(懒汉,线程安全)

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

在 getInstance() 里加了 synchronized。多线程能工作,也有 lazy loading,但因为锁效率很低,大多数情况下并不需要同步。

第三种(饿汉)

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

基于 classloader 机制避免了多线程同步问题。不过 instance 在类装载时就实例化,没有达到 lazy loading 的效果。

第四种(饿汉,变种)

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

表面差别挺大,其实和第三种差不多,都是类初始化即实例化 instance。

第五种(静态内部类)

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

同样利用 classloader 保证初始化 instance 时只有一个线程。和第三、四种的细微差别:那两种只要 Singleton 类被装载,instance 就会被实例化(没有 lazy loading);这种方式 Singleton 被装载了,instance 不一定初始化。因为 SingletonHolder 没有被主动使用,只有显式调用 getInstance 才会装载内部类,从而实例化。如果实例化很耗资源、想延迟加载,又不希望 Singleton 在别处被用到时就提前 new,这种方式比第三、四种更合理。

第六种(枚举)

public enum SingletonDemo6 {
    instance;
    public void whateverMethod(){
    }
}

这是 Effective Java 作者 Josh Bloch 提倡的方式:能避免多线程同步问题,还能防止反序列化重新创建对象。不过 enum 是 1.5 才加入的特性,实际工作中很少看见有人这么写。

第七种(双重校验锁)

public class SingletonDemo7 {
    private volatile static SingletonDemo7 singletonDemo7;
    private SingletonDemo7(){}
    public static SingletonDemo7 getSingletonDemo7(){
        if (singletonDemo7 == null) {
            synchronized (SingletonDemo7.class) {
                if (singletonDemo7 == null) {
                    singletonDemo7 = new SingletonDemo7();
                }
            }
        }
        return singletonDemo7;
    }
}

这是第二种的升级版,俗称双重检查锁定。详细介绍:http://www.ibm.com/developerworks/cn/java/j-dcl.html。在 JDK 1.5 之后,双重检查锁定才能够正常达到单例效果。

转至:https://www.cnblogs.com/Ycheng/p/7169381.html

一句话总结:单例保证一个类只有一个实例;懒汉延迟创建、饿汉类加载即创建,常用静态内部类或双重校验锁,枚举还能防反序列化。

转载请注明来源:常见的几种单例模式
本文链接地址:https://ai.zhousir.top/?p=2289
回复 取消