单例模式

113 阅读3分钟

什么是单例模式

单例模式(Singleton Pattern)是一种设计模式,用于确保一个类在整个应用程序运行期间只有一个实例,并且提供一个全局访问点来访问该实例。这种模式确保一个类只有一个实例存在,并提供一个访问它的全局变量。

举例:任务管理器(只能打开一个窗口)

单例模式的作用

  1. 控制实例数量
    • 确保某个类只有一个实例存在。对于一些管理类、资源类等,这种特性尤为重要。
  1. 全局访问点
    • 提供一个全局访问点,可以通过静态方法或变量来访问单例实例,简化了对象访问和管理。
  1. 节省资源
    • 由于只创建一个实例,可以避免创建多个实例带来的资源浪费,尤其是那些需要频繁访问但创建代价较高的类。
  1. 状态共享
    • 由于单例对象在应用程序中只有一个实例,可以通过它来共享状态和数据,避免了多实例带来的状态不一致问题。

饿汉

在类加载的同时创建实例

class Singleton {
    // 静态变量,类加载时创建实例
    private static final Singleton instance = new Singleton();

    // 私有构造函数,防止外部实例化
    private Singleton() {}

    // 提供全局访问点
    public static Singleton getInstance() {
        return instance;
    }
}

懒汉—单线程

在被使用的时候创建实例

class Singleton {
    private static Singleton instance = null;

    // 私有构造函数,防止外部实例化
    private Singleton() {}

    // 提供全局访问点
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

懒汉—多线程

上面的写法在多线程中其实会造成线程安全问题。如果两个线程同时判断instance == null,就会创建两次Singleton。

所以我们最简单的方法就是给方法加锁。

class Singleton {
    private static Singleton instance = null;

    // 私有构造函数,防止外部实例化
    private Singleton() {}

    // 提供全局访问点
    public synchronized static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

那么如果很多线程都来调用这个方法,竞争锁的频率就太高了。我们可以使用双重if来解决这个问题,在竞争前先判断一下。

class Singleton {
    private static volatile Singleton instance = null;

    // 私有构造函数,防止外部实例化
    private Singleton() {}

    // 提供全局访问点
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

如何理解呢?为什么要加volatile?

我们直接看运行步骤吧

  1. 假设有三个线程,同时执行getInstance,通过最外层的if判断知道实例还没有开始创建,于是开始竞争锁
  2. 假设线程1竞争到了锁,于是判断第二个if是否为true,如果为true开始实例化。因为加上了volatile所以变量在被修改时会及时更新到内存。
  3. 当线程1释放锁之后,线程2、3依次拿到锁,但此时第二层if判断为false。
  4. 其他线程后面再调用方法时第一层if就进不去,所以直接减少了锁的竞争。