单例模式
手写代码题要求你实现一个单例模式。这里重点强调的是,通常面试中要求实现的单例模式不是“饿汉模式”,而是“懒汉模式”,并且常常要求使用**双重检查锁(DCL)**来实现。
思路
首先,我们来梳理一下双重检查锁(DCL)的实现思路:
- 使用
volatile修饰单例对象:
为了保证单例对象的可见性和禁止指令重排序,我们需要使用volatile关键字修饰单例对象。这确保了在多线程环境下,当一个线程创建完单例对象并修改了对象引用后,其他线程能够立即看到这一修改。 - 双重检查锁(Double-Checked Locking) :
我们会在获取单例对象时,使用双重检查锁的方式来确保线程安全。在第一次检查时,如果单例对象已经创建,则直接返回;如果没有创建,则进入同步块。在同步块内部,我们再进行一次检查,以确保多个线程不会重复创建对象。
代码实现
以下是懒汉模式(使用双重检查锁)的单例模式Java代码实现:
public class Singleton {
// volatile关键字保证对象的可见性和禁止指令重排序
private static volatile Singleton instance;
// 私有构造函数,防止外部直接创建实例
private Singleton() {
// 防止反射创建新的实例
if (instance != null) {
throw new IllegalStateException("Cannot create instance, use getInstance()");
}
}
// 获取实例方法
public static Singleton getInstance() {
// 第一次检查:如果实例已创建,直接返回
if (instance == null) {
synchronized (Singleton.class) {
// 第二次检查:进入同步块后,仍需再次检查
if (instance == null) {
// 创建实例
instance = new Singleton();
}
}
}
return instance;
}
}
解释
volatile修饰实例变量:
volatile确保在多线程环境下,instance的修改对所有线程可见,防止因指令重排序导致的问题。- 第一次检查
instance == null:
在同步块外部,首先检查instance是否已创建。如果已经创建,直接返回实例,避免每次获取实例时都进入同步块。 - 同步块内的第二次检查:
进入同步块后,我们再次检查instance == null,以防止多个线程同时进入同步块并创建多个实例。这是“双重检查”的核心思想。 - 私有构造函数:
私有构造函数保证了外部无法直接创建实例,所有实例的创建都必须通过getInstance()方法。 - 防止反射:
通过在构造函数中添加检查(如果instance已经被创建,则抛出异常),避免通过反射创建多个实例。
小结
通过上述方式,我们实现了一个线程安全的单例模式,同时避免了不必要的同步开销,确保了高效性。双重检查锁(DCL)是一种常见的优化手段,尤其适用于需要延迟加载且只有一个实例的场景。