设计模式-单例模式(Singleton)

162 阅读4分钟

单例模式(Singleton)

Singleton:单例模式是一种创建型设计模式,保证一个类仅有一个实例,并提供一个访问它的全局访问点。

实现方式

懒汉模式

在第一次引用时,才会将自己实例化。

/**
 * 懒汉模式:线程不安全
 *
 * @author CAI
 * @time 2020/7/5
 */
public class LazySingleton {
​
    private static LazySingleton instance;
​
    private LazySingleton(){ }
​
    public static LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
​
        return instance;
    }
}
  • 优点:

    • 第一次调用时才会创建,避免资源浪费。
    • 加载类时速度快。
  • 缺点:

    • 线程不安全。
    • 运行时获得对象的速度比较慢。
  • 适用场景:

    • 内存占用大,对启动速度有要求。
    • 不适合多线程同时使用。

饿汉模式

在程序加载时就将自己实例化。

/**
 * 饿汉模式
 *
 * @author CAI
 * @time 2020/7/5
 */
public class EagerSingleton {
​
    private static final EagerSingleton instance = new EagerSingleton();
​
    private EagerSingleton(){ }
​
    public static EagerSingleton getInstance() {
        return instance;
    }
}
  • 优点

    • 线程安全。
    • 运行时获得对象的速度比较快。
  • 缺点

    • 占用资源。
    • 会增加程序的启动速度。
  • 适用场景:

    • 内存占用小,使用频繁。
    • 不适合使用频率低的场景使用。

静态块模式

饿汉模式的另一种实现方式。

/**
 * 静态块模式:同饿汉式
 *
 * @author Viices Cai
 * @time 2022/1/13
 */
public class StaticBlockSingleton {
​
    private static StaticBlockSingleton instance;
​
    private StaticBlockSingleton() { }
​
    static {
        try {
            instance = new StaticBlockSingleton();
​
        } catch (Exception e) {
            throw new RuntimeException("Exception occured in creating singleton instance.");
        }
    }
​
    public static StaticBlockSingleton getInstance() {
        return instance;
    }
}

同步锁模式

在懒汉模式的基础上解决了其线程不安全的问题,但是加锁也带来了额外的开销。

/**
 * 同步锁模式
 *
 * @author CAI
 * @time 2020/7/5
 */
public class LockSingleton {
​
    private static LockSingleton instance;
​
    private LockSingleton() {}
​
    public synchronized static LockSingleton getInstance() {
        if (instance == null) {
            instance = new LockSingleton();
        }
​
        return instance;
    }
}

双重锁定模式

双重检查模式,在同步锁的实现基础上进行了改进。两次判断:第一次是为了避免重复创建实例;第二次是为了进行同步,避免多线程问题,使得其线程安全,且在多线程下能保持高性能。

/**
 * 双重锁定
 *
 * @author CAI
 * @time 2020/7/5
 */
public class DoubleCheckLockingSingleton {
​
    private volatile static DoubleCheckLockingSingleton instance;
​
    private DoubleCheckLockingSingleton() { }
​
    public static DoubleCheckLockingSingleton getInstance() {
        if (instance == null) {
            synchronized (DoubleCheckLockingSingleton.class) {
                if (instance == null) {
                    instance = new DoubleCheckLockingSingleton();
                }
            }
        }
​
        return instance;
    }
}
  • 创建过程解析:

    1. 分配内存。

    2. 初始化对象:调用构造器。

    3. 指向刚分配的地址。

      • 若发生了指令重排序:即由于CPU调度问题执行顺序可能无法按照我们理想的情况进行,如:线程A执行了1、3此时未执行2,但是线程B进入了发现存在内存地址,但实际上对象并不存在。

      • 必须使用volatile避免重排序。

        • 禁止进行指令重排序。

使用场景

  1. 如果程序中的某个类对于客户端只有一个可用的实例,可以使用单例模式。

    • 单例模式禁止通过除特殊构建方法以外的任何方式来创建自身类的对象。 该方法可以创建一个新对象, 但如果该对象已经被创建, 则返回已有的对象
    • 确保一个类仅有一个实例,则表示其构造方法一定不能为public:不能被外界实例化private
  2. 如果你需要更加严格地控制全局变量, 可以使用单例模式。

    • 单例模式与全局变量不同, 它保证类只存在一个实例。 除了单例类自己以外, 无法通过任何方式替换缓存的实例。
    • IO访问数据库访问时,可以使用单例模式来减少资源的消耗。
  3. 当代码中出现链式传递,即:在主程序中进行初始化,通过不同方法的构造方法逐层传递的对象时可以考虑使用单例方法避免这一过程。

    • 避免在传递过程中造成的代码耦合。

优缺点

  • 优点:

    • 保证一个类只有一个实例。
    • 提供一个指向该实例的全局访问节点。
    • 仅在首次请求单例对象时对其进行初始化。
  • 缺点:

    • 违反了单一职责原则: 该模式同时解决了两个问题。
    • 单例模式可能会掩盖不良设计, 比如程序各组件之间耦合过多等。
    • 该模式在多线程环境下需要进行特殊处理, 实现上可能会比较复杂。