懒汉式单例模式:从多个方面深入阐述

发布时间:2023-05-24

在面向对象编程中,单例指的是一个类只允许创建一个实例,并且提供一个全局访问的接口。单例模式主要解决的问题是在多线程环境下,如何保证一个类只有一个实例,并且能够在全局范围内访问该实例。懒汉式单例模式是其中一种最为常见的实现方式。

一、基本定义

懒汉式单例模式也称为“懒加载”或“延迟初始化”,其主要思想是:只有当第一次请求实例时,才会实例化对象,避免了类在初始化时就实例化对象,从而影响应用的启动效率。

public class Singleton {
    private static Singleton instance;
    private Singleton() {}
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

在上面的代码中,instanceSingleton 类的一个私有静态变量,用于存储该类的唯一实例。getInstance() 方法是获取该实例的唯一接口。在每次调用 getInstance() 方法时,都会先判断 instance 是否为空,如果为空,则创建一个新的实例,否则直接返回已存在的实例。

二、线程安全

由于多线程环境下有可能出现竞态条件,从而导致两个线程同时执行到 instance == null 的判定语句,从而实例化两个实例,所以懒汉式单例模式的线程安全性较差。 为了避免这种问题,可以采用多种方式对 getInstance() 方法进行线程安全的设计。下面介绍三种常用的线程安全方案:

1. synchronized关键字

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

通过在 getInstance() 方法上添加 synchronized 关键字,可以将该方法变为同步方法,从而保证了只有一个线程能够同时进入该方法进行实例化。但是由于每次调用该方法都需要获得锁,所以会造成较大的性能损耗。

2. Double Check Lock(双重校验锁)

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

在 Double Check Lock 方案中,同样是通过使用 synchronized 关键字对 instance 的实例化进行加锁,但是只有在 instancenull 时,才会进入 synchronized 块进行实例化。这种方式既保证了线程安全性,又可以避免多次获取锁造成的性能损耗。

3. 静态内部类

public class Singleton {
    private Singleton() {}
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

在静态内部类方案中,Singleton 类中的 instance 被声明为私有,并且新建了一个静态内部类 SingletonHolder,在 SingletonHolder 类中声明了一个静态的、final 的、并且是 Singleton 类型的变量 INSTANCE。在 getInstance() 方法中,直接返回 SingletonHolder.INSTANCE,从而保证了线程安全性,同时也可以达到懒加载的效果。

三、序列化与反序列化

在使用单例模式时,有时需要对单例对象进行序列化或反序列化,但是由于单例类的构造函数被私有化,并且 getInstance() 方法是静态方法,所以需要对 Singleton 类进行特殊处理才能保证序列化的正确性。以下是一种序列化与反序列化的方式:

public class Singleton implements Serializable {
    private static Singleton instance = new Singleton();
    private Singleton() {}
    public static Singleton getInstance() {
        return instance;
    }
    private Object readResolve() throws ObjectStreamException {
        return instance;
    }
}

在上述代码中,我们将 instance 变量声明为静态变量,并在定义时就实例化了对象。为了保证反序列化时不会创建新的实例,我们定义了一个 readResolve() 方法,将反序列化返回的对象直接指向 instance,从而保证了单例的正确性。

四、反射

在 Java 的反射机制中,可以通过调用构造函数的 newInstance() 方法来创建一个类的实例。对于懒汉式单例模式而言,如果想要通过反射来创建新的实例,则需要对代码进行特殊的设计。

public class Singleton {
    private static Singleton instance = new Singleton();
    private Singleton() {
        if (instance != null) {
            throw new IllegalStateException("Already initialized.");
        }
    }
    public static Singleton getInstance() {
        return instance;
    }
    private Object readResolve() throws ObjectStreamException {
        return instance;
    }
}

在上面的代码中,我们在 Singleton 类的私有构造函数中加入了一个判断语句,判断 instance 是否为 null。如果 instance 已经被实例化,则抛出 IllegalStateException 异常,从而避免通过反射创建新的实例。

五、结论

懒汉式单例模式是一种实现单例模式的常见方式,其主要思想是在需要时才进行实例化。但是由于多线程环境下可能存在线程安全问题,需要进行特殊的处理。此外,序列化与反序列化以及反射都可能对懒汉式单例模式带来一些问题,需要进行特殊的设计和处理。