设计模式-单例模式
标签:设计模式

单例模式的七种写法

Ensure a class has only one instance,and provide a global point of access to it.
确保某一个类只有一个实例,并且自行实例化并向整个系统提供这个实例**

优点:

  1. 单例模式内存中只有一个实例,减少了内存开支,特别是一个对象需要频繁的创建、销毁时,
    而且创建或销毁的性能又无法优化。单例的优势就很明显了
  2. 减少了系统的性能开销,当一个对象产生较多资源时,或者读取配置时,可以在启动的时候,
    生成一个对象,让它永驻内存来解决。
  3. 可以避免一个对资源的多重占用,比如写文件,由于只有一个实例,避免了对一个文件同时写操作
  4. 可以在系统设置全局的访问点,优化和共享资源访问,例如可以设计一个单例类,
    负责所有数据表的映射处理

缺点:

  1. 单例模式一般没有接口,扩展很困难,若要扩展的话,只有修改源代码,为什么不设置接口,
    因为单例要自行实例化,接口或抽象类是不能被实例化的。
  2. 单例模式对测试是不利的,在并行环境中,如果单例模式没有完成,是不能进行测试的。
  3. 单例模式与单一职责原则有冲突,一个类应该只有一个实现逻辑,而不关心它是否是是单例的,
    是不是单例要取决于环境,单例模式吧“要单例”和业务逻辑融合在一个类中。

使用场景:

在一个系统中,要求一个类有且仅有一个对象,如果出现多个对象的话,就会出现“不良反应”,
可以采用单例模式。

  1. 要求生成为唯一序列号的环境
  2. 在整个项目中需要一个共享访问点或共享数据,例如一个Web页面的计数器,可以不用把值写入到数据库,
    利用单例模式保存
  3. 创建一个对象需要消耗过多的资源,比如访问I/O或者数据库
  4. 需要定义大量的静态常量,和静态方法(如工具类)等。

写法:

写法一:饱汉模式

/**
 * 饱汉模式,核心是懒加载,好处是启动快,节约资源,一直到实例被第一次访问的时候,才初始化。
 * 坏处主要是线程不安全。if存在竟态条件
 */
public class Singleton1 {
    public static Singleton1 singleton1=null;
    private Singleton1(){

    }
    public static Singleton1 getInstance(){
        //if存在竟态条件,当两个线程同时执行道这里的时候就会出错
        if (singleton1==null){
            singleton1=new Singleton1();
        }
        return singleton1;
    }
}

写法二:饱汉模式变种一

/**
 * 饱汉模式变种1,添加Synchronized修饰,则线程安全,坏处是并发性能差
 */
public class Singleton1_1 {
    public static Singleton1_1 singleton1_1=null;
    private Singleton1_1(){

    }
    public synchronized static Singleton1_1 getInstance(){
        if (singleton1_1==null){
            singleton1_1=new Singleton1_1();
        }
        return singleton1_1;
    }
}

写法三: 饱汉模式DCL 1.0

/**
 * 饱汉模式变种2 DCL 1.0 在变种1的基础上又套了一次check,Double Check Lock DCL
 * 似乎达到了懒加载+线程安全,但是由于指令重排序,有可能得到是半个对象
 */
public class Singleton1_2 {
    public static Singleton1_2 singleton1_2=null;
    private Singleton1_2(){

    }
    public static Singleton1_2 getInstance(){
        if (singleton1_2==null){
            synchronized (Singleton1_2.class){
                if (singleton1_2==null){
                    singleton1_2=new Singleton1_2();
                }
            }
        }
        return singleton1_2;
    }
}

写法四:饱汉模式 DCL 2.0

/**
 * 饱汉模式变种2 DCL 2.0 在变种1_2上加上一个volatile
 */
public class Singleton1_3 {
    public static volatile Singleton1_3 singleton1_3=null;
    private Singleton1_3(){

    }
    public static Singleton1_3 getInstance(){
        if (singleton1_3==null){
            synchronized (Singleton1_3.class){
                if (singleton1_3==null){
                    singleton1_3=new Singleton1_3();
                }
            }
        }
        return singleton1_3;
    }
}

写法五:饿汉模式

/**
 * 饿汉模式,好处是线程安全,使用时没有延迟,坏处是可能造成资源浪费。
 */
public class Singleton2 {
    public static final Singleton2 singleton2=new Singleton2();
    private Singleton2(){

    }
    public static Singleton2 getInstance(){
        return singleton2;
    }
}

写法六:Holder模式

/**
 * Holder模式,希望利用饿汉模式中静态变量的方便和线程安全,又希望通过懒加载规避资源浪费,
 * Holder模式满足了这两点需求,核心仍是静态变量,足够方便和安全,通过静态的Holder类持有真正实例
 * 间接满足了懒加载。
 */
public class Singleton3 {
//    通过静态的内部类持有真正实例
    private static class SingletonHolder{
        private static final Singleton3 singleton3=new Singleton3();
        private SingletonHolder(){

        }
    }
    private Singleton3(){

    }


    public static Singleton3 getInstance(){
//        通过Holder来懒加载
        return SingletonHolder.singleton3;
    }

}

写法七:枚举模式

/**
 * 丑类但好用的语法糖,通过jsd反编译,得到的与饿汉模式相同,不过区别在于
 * 枚举的方式是公用的静态变量。
 */

// 定义单例模式中需要完成的代码逻辑
public interface MySingleton {
    void doSomething();
}

public enum Singleton implements MySingleton {
    INSTANCE {
        @Override
        public void doSomething() {
            System.out.println("complete singleton");
        }
    };

    public static MySingleton getInstance() {
        return Singleton.INSTANCE;
    }
}

或者

public enum  Singleton4 {
    SINGLETON_4;
    private SingletonExample singleton;
    //JVM保证这个方法绝对只执行一次
    Singleton4{
        singleton=new SingletonExample();
    }
    public SingletonExample getInstance(){
        return singleton;
    }
}

//调用方式
public class SingletonExample{
    private SingletonExample(){}
    
    public static SingletonExample getInstance(){
        return Singleton4.SINGLETON_4.getInstance();
    }
}

通过反编译(jad,源码|String拼接操作”+”的优化?也用到了)打开语法糖,就看到了枚举类型的本质,简化如下:

// 枚举
// ThreadSafe
public class Singleton4 extends Enum<Singleton4> {
  ...
  public static final Singleton4 SINGLETON = new Singleton4();
  ...
}

本质上和饿汉模式相同,区别仅在于公有的静态成员变量。

实现方式 关键点 资源浪费 线程安全 多线程环境的性能足够优化
基础饱汉 懒加载 -
饱汉变种1 懒加载、同步
饱汉变种2 懒加载、DCL -
饱汉变种3 懒加载、DCL、volatile
饿汉 静态变量初始化
Holder 静态变量初始化、holder
枚举 枚举本质、静态变量初始化

通过以上两篇文章,让我对单例模式有了更加深入的理解,收集整理一波,以便后面回顾。

安全性问题

原来没有考虑过单例模式的安全性问题,但其实通过反射是可以破坏它的单例的特性的

一个饿汉模式:

public class Singleton {
    public static Singleton singleton=new Singleton();

    private Singleton(){}

    public static Singleton getSingleton() {
        return singleton;
    }
}

下面通过反射代码测试:

public class Main {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Singleton singleton=Singleton.getSingleton();

        Constructor constructor=Singleton.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        Singleton singleton1= (Singleton) constructor.newInstance();
        System.out.println(singleton == singleton1);

    }
}

用枚举解决

参考文章:《设计模式之禅》—秦小波著 和 猴子007 的文章 面试中单例模式有几种写法?

  • 6 min read

CONTRIBUTORS


  • 6 min read