设计模式 - 单例模式

120 阅读8分钟

设计模式 - 单例模式

一、引入

当我们写程序时,有时候需要确保某个类只有一个实例,而不会被重复创建。这就像是一家商店只能有一个老板,不能有多个一样。

单例模式就是为了解决这个问题而存在的一种设计方法。它保证了一个类只会有一个实例,并提供了一个全局的方式让其他地方可以获取到这个实例。

比如,你在写一个游戏,游戏里只能有一个主角角色,那么你可以用单例模式来确保只有一个主角的实例存在,而不会出现多个相同的主角。

单例模式的核心就是:

  1. 确保只有一个实例。
  2. 提供一个方法让其他地方可以获取到这个实例。

这样做的好处是可以节省资源,因为只有一个实例,不会重复占用内存等资源。同时也方便了在程序的各个地方使用这个实例。

但是要小心使用,不合适的地方用单例模式可能会导致程序设计变得复杂和难以理解。

二、概念

单例模式是一种创建型设计模式,它确保一个类只有一个实例,并提供了一个全局访问点来访问该实例。

三、基本要素

  • 1、单例类只能有一个实例。
  • 2、单例类必须自己创建自己的唯一实例。
  • 3、单例类必须给所有其他对象提供这一实例。

四、示例代码

六种方式创建单例模式

1、懒汉式 (线程不安全)

是一种延迟加载的单例模式,实例在第一次被需要时才会被创建,而不是在程序启动时就立即创建。

这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为多个线程可能会同时进入 getInstance 方法,导致创建多个实例。

/**
 * 1、懒汉式
 *
 * @author zf
 */
public class LazySingleton {

    // 私有静态变量,用于保存唯一实例的引用
    private static LazySingleton instance;

    // 私有构造函数,确保其他类不能直接实例化该类
    private LazySingleton() {
        // 可以在构造函数中进行一些初始化操作
    }

    // 静态方法,用于获取该类的唯一实例
    public static LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}

2、懒汉式(线程安全)

/**
 * 2、懒汉式(线程安全)
 *
 * @author zf
 */
public class LazySingletonSafe {

    // 私有静态变量,用于保存唯一实例的引用
    private static LazySingletonSafe lazySingletonSafe;

    // 私有构造方法,防止被实例化
    private LazySingletonSafe() {
        // 可以在构造函数中进行一些初始化操作
    }

    // 静态方法,用于获取该类的唯一实例
  	// 加锁 synchronized 保证单例,但加锁会影响效率。
    public static synchronized LazySingletonSafe getInstance() {
        if (lazySingletonSafe == null) {
            lazySingletonSafe = new LazySingletonSafe();
        }
        return lazySingletonSafe;
    }
}

3、饿汉式

饿汉式单例模式是一种在类加载时就创建实例的单例模式。它保证了在程序启动时就会有一个唯一的实例存在。

饿汉式单例模式的优点是实现简单,而且线程安全(因为实例在类加载时就被创建,不会存在多线程竞争创建实例的情况)。

然而,它的缺点是在程序启动时就会创建实例,可能会占用一些不必要的资源,特别是当实例的初始化比较耗时时。

总的来说,饿汉式单例模式适用于实例初始化比较简单且不会消耗过多资源的情况。

/**
 * 3、饿汉式
 *
 * @author zf
 */
public class EagerSingleton {

    // 在类加载时就创建实例
    private static final EagerSingleton instance = new EagerSingleton();

    // 私有构造函数,确保其他类不能直接实例化该类
    private EagerSingleton() {
        // 可以在构造函数中进行一些初始化操作
    }

    // 静态方法,用于获取该类的唯一实例
    public static EagerSingleton getInstance() {
        return instance;
    }
}

4、双重锁校验

在这种模式中,只有在第一次获取实例时才会创建实例,避免了在程序启动时就创建实例的资源浪费,同时又保证了线程安全。

双重校验锁单例模式是一种非常优秀且常用的单例模式,适用于大多数情况。

/**
 * 4、双重校验锁
 *
 * @author zf
 */
public class DoubleCheckedSingleton {
    
    // volatile 关键字确保了 instance 对象的可见性
    private static volatile DoubleCheckedSingleton instance;

    // 私有构造函数,确保其他类不能直接实例化该类
    private DoubleCheckedSingleton() {
        // 可以在构造函数中进行一些初始化操作
    }

    // 静态方法,用于获取该类的唯一实例
    public static DoubleCheckedSingleton getInstance() {
        if (instance == null) {
            synchronized (DoubleCheckedSingleton.class) {
                if (instance == null) {
                    instance = new DoubleCheckedSingleton();
                }
            }
        }
        return instance;
    }
}

5、静态内部类

静态内部类单例模式是一种利用Java的类加载机制来保证线程安全且实现延迟加载的单例模式。

在这种模式中,单例的实例被保存在一个静态内部类中,只有当第一次访问这个内部类时,才会触发内部类的加载,从而初始化单例实例。这样可以保证在多线程环境下,实例只会被创建一次。

这种方式既保证了线程安全,又实现了延迟加载,因此是一种非常优秀的单例模式实现方式。

总的来说,静态内部类单例模式是一种推荐的单例模式,可以在大多数情况下使用。

/**
 * 5、静态内部类
 *
 * @author zf
 */
public class StaticInnerClassSingleton {

    //私有构造函数,确保其他类不能直接实例化该类
    private StaticInnerClassSingleton() {
        // 可以在构造函数中进行一些初始化操作
    }

    // 静态内部类,用于保存单例实例
    private static class sinletonHelper {
        private static final StaticInnerClassSingleton INSTANCE = new StaticInnerClassSingleton();
    }

    // 静态方法,用于获取该类的唯一实例
    public static StaticInnerClassSingleton getInstance() {
        return sinletonHelper.INSTANCE;
    }
}

6、枚举

枚举单例模式是一种简单且线程安全的单例模式实现方式,它基于枚举类型的特性,保证了在任何情况下都只会有一个实例存在。

枚举单例模式的优点是简单、线程安全、不容易被破坏(例如反射、序列化等方式)。

使用枚举单例模式时,可以像使用其他单例模式一样调用它的方法,也可以添加其他成员变量和方法来扩展其功能。

总的来说,枚举单例模式是一种非常优秀且推荐的单例模式实现方式,特别适用于需要保证线程安全且简单的单例情况。

/**
 * 6、枚举
 *
 * @author zf
 */
public enum EnumSingleton {
    // 单例实例
    INSTABCE;

    // 可以在枚举中添加其他成员变量和方法
    private int value;

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }
}

五、用途

  1. 资源共享与控制
    • 单例模式可以用于管理共享的资源,例如数据库连接池、线程池等。通过单例模式,可以确保所有的组件共享同一个资源池,避免了资源的浪费和重复创建。
  2. 配置管理器
    • 用单例模式来实现配置管理器,确保在应用程序中只有一个全局的配置信息,方便统一管理。
  3. 日志管理器
    • 通常在应用程序中只需要一个日志管理器来记录日志信息,通过单例模式可以确保只有一个日志实例存在。
  4. 线程池管理
    • 通过单例模式可以实现线程池的单例,确保所有任务共享同一个线程池。
  5. 缓存管理
    • 一些缓存管理器需要保证只有一个实例,通过单例模式可以实现。
  6. Spring框架中的Bean管理
    • Spring框架中的Bean默认是单例的,容器中每个Bean的作用域默认为Singleton,确保在Spring容器中只有一个实例。
  7. Servlet容器中的Servlet实例
    • Servlet容器会保证在一个Web应用程序中只会创建一个Servlet实例,确保共享资源。
  8. 数据库连接池
    • 数据库连接池是一种常用的单例模式的应用场景,确保在应用程序中只有一个连接池实例。
  9. 日历对象
    • 在一些应用中需要使用日历对象来进行日期时间的操作,通过单例模式可以确保只有一个全局的日历对象实例。

六、总结

单例模式类型优点缺点
饿汉式单例模式- 实现简单
- 线程安全
- 在程序启动时就创建实例,保证了实例的唯一性
- 可能会浪费一些内存,特别是在实例的初始化比较耗时的情况下
懒汉式单例模式- 延迟加载,只有在第一次获取实例时才会创建,节省了资源- 需要考虑线程安全问题
- 在高并发环境下,性能可能会受到影响
双重校验锁单例模式- 实现了懒加载和线程安全
- 性能相对较好
- 实现稍微复杂一些,需要考虑 volatile 关键字的使用
静态内部类单例模式- 基于类加载机制,保证了线程安全和延迟加载- 相对较少使用,不如前三者常见
枚举单例模式- 简单、线程安全、不容易被破坏- 无法延迟加载,实例会在枚举类被加载时创建