《设计模式》建立型-单列模式及各类实现

定义

确保某一个类只有一个实例,并且自行实例化并向整个系统提供这个实例java

使用场景

确保某一个类有且只有一个对象的场景,避免产生多个对象消耗过多的资源,或者某种类型的对象只应该有且只有一个。例如要访问IO和数据库等资源,这时候就能够考虑使用单列模式。数据库

UML 类图

classDiagram
Singleton <.. Client
class Singleton{
+getInstanc()Singleton
-Singleton()
}

须要注意的几个关键点:

  1. 构造函数不对外开发,一般为Private;
  2. 经过静态方法或者枚举返回单列类对象;
  3. 确保单列类有且只有一个对象,尤为是在多线程的环境下;
  4. 确保单列类对象在反序列化时不会从新构建。

单列的实现方式

  1. 饿汉模式
public class Singleton{
    private static Singleton instance = new Singleton();
    private Singleton(){}
    public static Singleton getInstance(){
        return instance;
    }
}
复制代码

这种方式在类加载的时候就自行实例化,避免了多线程同步的问题。安全

  1. 懒汉模式
public class Singleton{
    private static Singleton instance = null;
    private Singleton(){}
    public static synchronized Singleton getInstance(){
        if(instance == null){
            instance = new Singleton();
        }
        return instance;
    }
}
复制代码

这种方式的优势是只有在第一次使用的时候才会被实例化,能够看到 getInstance() 添加了 synchronized 修饰,它是一个同步方法,这是为了在多线程的状况下保证单列对象的惟一性,若是只在单线程使用能够不加。也是由于添加了 synchronized 致使每次调用getInstance()都须要进行同步,致使没必要要的同步开销,因此不建议使用这种写法。markdown

  1. 双重检查模式 DCL(Double Check Lock)
public class Singleton{
    private volatile static Singleton instance = null;
    private Singleton(){}
    public static Singleton getInstance(){
        if(instance == null){
            synchronized(Singleton.class){
                if(instance == null){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}
复制代码

这种方式相比上面的避免了不必的同步,同时也能作到线程安全。能够看到有两次判空,第一次试为了不没必要要的同步,第二次是为了确保单列对象的惟一性。这里仍是用了 volatile 关键字,若是不用就有可能出现DCL 失效的问题。多线程

是什么致使DCL 失效的?咱们接着往下看。函数

假设A线程执行到 instance = new Singleton() 这行代码,这行代码最终会被编译成多条指令,大体作如下3键事情:性能

  1. 给Singleton 实例分配内存
  2. 调用 Singleton() 构造函数,实例化对象
  3. 将 instance 对象指向分配的内存空间 (这时候 instance 就不为 null了)

可是因为Java 编译器容许处理器乱序执行,这时候就有可能出现执行顺序为1-3-2,当A线程执行完3还未执行2的时候,B线程取走了instance ,在使用时就会出错,这就是DCL 失效问题。而使用volatile 关键字修饰能够禁止进行指令重排序,全部能够有效的避免这个问题。固然使用volatile 也会对性能有一点影响,但考虑程序的正确性,这点牺牲是值得的。spa

  1. 静态内部类模式
public class Singleton{
    private Singleton(){}

    public static Singleton getInstance(){
        return SingletonHolder.INSTANCE;
    }
    
    private static class SingletonHolder{
        private static final Singleton INSTANCE = new Singleton();
    }
}
复制代码

由于静态内部类不会由于外部类的加载而加载,静态内部类加载不须要依附于外部类,但在加载静态内部类时必定会加载外部类。线程

所以使用这种方式能够确保线程安全,也能保证单列对象的惟一性,同时也延迟了单列的实例化,也不会有性能影响。因此这是最推荐的使用方式。code

  1. 枚举模式
public enmu Singleton{
    INSTANCE;
    public void doSomething(){
    }
}
复制代码

枚举默认是线程安全的并且反序列化也不会致使从新建立对象,保证单列对象的惟一性。若是以前4种模式要杜绝反序列化时从新生成对象,那么必须加入 readResolve() 函数。

public class Singleton implements Serializable{
    private static Singleton instance = new Singleton();
    private Singleton(){}
    public static Singleton getInstance(){
        return instance;
    }
    
    private Object readResolve() throws ObjectStreamException{
        return instance;
    }
}
复制代码

小结

以上介绍了单列的5种实现方式,并不是所有。就我的而言,不考虑懒加载的状况下使用 饿汉模式 便可,不然建议使用 静态内部类模式,若是须要考虑反序列化的状况能够考虑 枚举模式 ,枚举模式由于可读性不太好,因此通常用的比较少。

相关文章
相关标签/搜索