单例模式(Singleton)——对象创建型模式

268 阅读3分钟

模式意图

保证一个类只有一个实例,并提供访问这个实例的全局访问点

模式结构

image.png

代码示例(饿汉式)

// 这种方式存在一个弊端
// 在 HungerSingleton 类加载进内存的时候
// 会立即被实例化
public class HungerSingleton {
    private static final HungerSingleton hungerSingleton = new HungerSingleton();

    public int age;

    private HungerSingleton() {}

    public static HungerSingleton getHungerSingleton() {
        return hungerSingleton;
    }

    public static void main(String[] args) {
        System.out.println(HungerSingleton.getHungerSingleton());
    }
}

代码示例(懒汉式)

package com.tripp.singleton;

public class LazySingleton {
    // volatile 用于 JIT 编译优化
    public static volatile LazySingleton lazySingleton = null;
    private int age;

    public static LazySingleton getLazySingleton() {
        if (lazySingleton == null) {
            synchronized (LazySingleton.class) {
                if (lazySingleton == null) {
                    lazySingleton = new LazySingleton();
                }
                return lazySingleton;
            }
        }
        return lazySingleton;
    }

    public static void main(String[] args) {
        System.out.println(LazySingleton.getLazySingleton());
    }
}

适用场景

  1. 系统只需要一个实例对象,如提供一个唯一序号的生成器或者考虑系统资源消耗太大而只允许创建一个对象
  2. 客户端调用单个实例只允许存在一个访问点,除了该公共点,不能通过其他途径访问,如果此时你不期望使用全局变量,可以使用单例模式(单例模式与全局变量相比,它保证了创建出来的实例不会被覆盖)

场景案例

  1. 在一个系统中只允许存在一个打印池对象

模式效果(优缺点)

  1. 提供对唯一实例的受控访问,所以它可以严格控制用户怎样以及何时访问
  2. 由于系统中只存在一个实例对象,可以节约系统的资源
  3. 在此基础之上,我们可以允许可变数目对实例,使用与单例相似的方法来控制对象生成的个数
  4. 由于单例模式没有抽象层,因此单例类的扩展有很大的困难
  5. 单例类的职责过重,违背了单一职责原则,因为单例类既充当了工厂角色,提供工厂方法,又包含产品创建的业务逻辑
  6. 允许对操作和表示的精化(Singleton 类可以有子类,而且使用这个扩展类的实例来配置应用是很容易的)
  7. 滥用单例会带来一些负面问题,比如说,为了节省资源将数据库连接池设计为单例类,会导致共享连接池对象的程序过多而出现连接池溢出;很多面向对象(Java、C#)都提供了自动垃圾回收技术,如果长时间没有使用单例对象就会被自动回收,新创建的实例对象会丢失掉原来的状态
  8. 单例类的客户端代码的单元测试可能会比较麻烦,由于单例类的构造函数是私有的
  9. 在多线程的场景下需要特殊处理,防止多个线程多次创建实例

模式扩展

  1. 外观类可以转换为单例类,在大部分场景下一个外观对象就足够了

  2. 如果你能将所有对象的共享状态简化为一个享元对象,那么享元就跟单例类似了,但这两个模式有根本性的不同

    1. 单例只有一个类实例,享元可以有多个实例,每个实例内部状态也可以不相同
    2. 单例对象是可变的,享元对象是不可变的
  3. 抽象工厂生成器原型模式都可以通常单例模式来实现