crazyandcoder

设计模式教程(1-单例模式)

2021.06.30

单例模式.png

1 定义

单例模式(Singleton Pattern):单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。

2 UML图

uml.gif

3 实现方式

目前来讲,单例模式主要有以下五种实现方式:

3.1 饿汉式

饿汉式是一种比较简单,比较好的实现方式。它的实现方式比较简单,就是在类加载的时候,instance 静态实例就已经创建并初始化好了,所以,instance 实例的创建过程是线程安全的。不过,这样的实现方式不支持延迟加载(即在真正用到类实例的时候,再创建实例)。


public class LazySingleInstance {

    private LazySingleInstance() {
    }

    private static LazySingleInstance instance = new LazySingleInstance();

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

这种方式创建的单例有好有坏,好处是:实现简单,线程安全。缺点是:不支持懒加载

3.2 懒汉式

懒汉式相对于饿汉式,它支持延迟加载:


public class HungrySingleInstance {

    private HungrySingleInstance() {
    }

    private static HungrySingleInstance instance = null;

    public synchronized static HungrySingleInstance getInstance() {
        if (instance == null) {
            instance = new HungrySingleInstance();
        }
        return instance;
    }
}

不过懒汉式的缺点很明显,在调用 getInstance() 方法时加了一把锁 synchronized 如果是频繁调用的话就会产生性能问题。

3.3 双重检测

相对于前面两种模式,饿汉式不支持懒加载,懒汉式性能有问题,而双重检测实现的单例模式则同时兼顾两者的优点,即支持懒加载,又支持高并发。


public class DoubleCheckSingleInstance {

    private static volatile DoubleCheckSingleInstance instance;

    private DoubleCheckSingleInstance() {

    }

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

在第一行中,我们给 instance 变量加上了 volatile 关键字,主要是为了解决指令重排的问题。因为指令重排序,可能会导致 DoubleCheckSingleInstance 对象被 new 出来,并且赋值给 instance 之后,还没来得及初始化(执行构造函数中的代码逻辑),就被另一个线程使用了。要解决这个问题,我们需要给 instance 成员变量加上 volatile 关键字,禁止指令重排序才行。

3.4 静态内部类

静态内部类方式实现的单例模式比双重检测方式更简单。它类似于饿汉式同时又做到了支持懒加载:


public class StaticInnerSingleInstance {

    private  StaticInnerSingleInstance(){

    }
    private static class StaticInnerClassHelper{
        private static final StaticInnerSingleInstance instance =new StaticInnerSingleInstance();
    }

    public static StaticInnerSingleInstance getInstance(){
        return StaticInnerClassHelper.instance;
    }

}

StaticInnerClassHelper 是一个静态内部类,当外部类 StaticInnerSingleInstance 被加载的时候,并不会创建 StaticInnerClassHelper 实例对象。只有当调用 getInstance() 方法时, StaticInnerClassHelper 才会被加载,这个时候才会创建 instance。instance 的唯一性、创建过程的线程安全性,都由 JVM 来保证。所以,这种实现方法既保证了线程安全,又能做到延迟加载。

3.5 枚举类

基于枚举类型的单例实现是通过 Java 枚举类型本身的特性,从而保证了实例创建的线程安全性和实例的唯一性:


public enum EnumSingleInstance {

    INSTANCE;

}

4 优缺点

4.1 优点

  1. 单例模式可以保证内存里只有一个实例,减少了内存的开销。
  2. 可以避免对资源的多重占用。
  3. 单例模式设置全局访问点,可以优化和共享资源的访问。

4.2 缺点

  1. 单例模式一般没有接口,扩展困难。如果要扩展,则除了修改原来的代码,没有第二种途径,违背开闭原则。
  2. 在并发测试中,单例模式不利于代码调试。在调试过程中,如果单例中的代码没有执行完,也不能模拟生成一个新的对象。
  3. 单例模式的功能代码通常写在一个类中,如果功能设计不合理,则很容易违背单一职责原则。

5 总结

单例模式有下面几种经典的实现方式。

(1)饿汉式
饿汉式的实现方式,在类加载的期间,就已经将 instance 静态实例初始化好了,所以,instance 实例的创建是线程安全的。不过,这样的实现方式不支持延迟加载实例。

(2)懒汉式
懒汉式相对于饿汉式的优势是支持延迟加载。这种实现方式会导致频繁加锁、释放锁,以及并发度低等问题,频繁的调用会产生性能瓶颈。

(3)双重检测
双重检测实现方式既支持延迟加载、又支持高并发的单例实现方式。只要 instance 被创建之后,再调用 getInstance() 函数都不会进入到加锁逻辑中。所以,这种实现方式解决了懒汉式并发度低的问题。

(4)静态内部类
利用 Java 的静态内部类来实现单例。这种实现方式,既支持延迟加载,也支持高并发,实现起来也比双重检测简单。

(5)枚举
最简单的实现方式,基于枚举类型的单例实现。这种实现方式通过 Java 枚举类型本身的特性,保证了实例创建的线程安全性和实例的唯一性。