1. 单例模式的基本概念与核心价值
单例模式(Singleton Pattern)是设计模式中最简单却最常被问及的模式之一。它的核心在于确保一个类只有一个实例,并提供一个全局访问点。这个设计模式在需要控制资源访问、配置管理或线程池等场景中尤为重要。
为什么面试官如此钟爱这个问题?因为单例模式看似简单,实则暗藏玄机。它能考察面试者对线程安全、类加载机制、反射攻击防御、序列化问题等多个知识点的理解深度。在实际工程中,我曾见过因为单例实现不当导致的内存泄漏和性能问题,这些血泪教训让我深刻认识到"简单"背后的复杂性。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 饿汉式:最直接的单例实现
2.1 基础饿汉式实现
java复制public class EagerSingleton {
private static final EagerSingleton instance = new EagerSingleton();
private EagerSingleton() {}
public static EagerSingleton getInstance() {
return instance;
}
}
这是最简单的实现方式,利用JVM类加载机制保证线程安全。当类被加载时,静态变量instance就会被初始化。这种方式的优点是实现简单,线程安全;缺点是如果实例未被使用,会造成内存浪费。
我在早期项目中使用这种实现时,曾遇到过一个典型问题:当单例类初始化耗时较长时,会导致应用启动变慢。后来我们改用懒加载方式解决了这个问题。
2.2 静态代码块变体
java复制public class StaticBlockSingleton {
private static final StaticBlockSingleton instance;
static {
try {
instance = new StaticBlockSingleton();
} catch (Exception e) {
throw new RuntimeException("创建单例失败", e);
}
}
private StaticBlockSingleton() {}
public static StaticBlockSingleton getInstance() {
return instance;
}
}
这种变体允许我们在静态块中进行异常处理,适合初始化过程可能抛出异常的场景。我在一个需要读取配置文件的单例中就采用了这种方式。
3. 懒汉式:按需创建的单例实现
3.1 基础懒汉式(线程不安全)
java复制public class LazySingleton {
private static LazySingleton instance;
private LazySingleton() {}
public static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
这种实现方式延迟了实例创建,但存在严重的线程安全问题。当多个线程同时检查instance为null时,可能会创建多个实例。我在一次代码审查中就发现过这种问题,导致系统出现了难以追踪的bug。
3.2 同步方法懒汉式
java复制public class SynchronizedSingleton {
private static SynchronizedSingleton instance;
private SynchronizedSingleton() {}
public static synchronized SynchronizedSingleton getInstance() {
if (instance == null) {
instance = new SynchronizedSingleton();
}
return instance;
}
}
通过给getInstance方法加synchronized关键字解决了线程安全问题,但每次获取实例都需要同步,性能较差。在高并发场景下,这种实现会成为性能瓶颈。我在一个高频调用的服务中曾测量过,这种实现比DCL(双重检查锁)慢了近10倍。
4. 双重检查锁定(DCL)实现
java复制public class DCLSingleton {
private static volatile DCLSingleton instance;
private DCLSingleton() {}
public static DCLSingleton getInstance() {
if (instance == null) {
synchronized (DCLSingleton.class) {
if (instance == null) {
instance = new DCLSingleton();
}
}
}
return instance;
}
}
这是目前最完善的懒加载单例实现之一。需要注意的关键点:
- volatile关键字防止指令重排序
- 两次null检查确保线程安全
- 同步块只在第一次创建实例时执行
我在金融交易系统中就采用了这种实现,既保证了线程安全,又不会明显影响性能。但要注意,在Java 5之前,由于JMM(Java内存模型)的问题,这种实现仍然可能失效。
5. 静态内部类实现
java复制public class InnerClassSingleton {
private InnerClassSingleton() {}
private static class SingletonHolder {
private static final InnerClassSingleton INSTANCE = new InnerClassSingleton();
}
public static InnerClassSingleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
这是我最推荐的单例实现方式之一,它结合了饿汉式的线程安全优势和懒汉式的延迟加载优势。静态内部类只有在被引用时才会加载,完美实现了懒加载。我在多个生产项目中都采用了这种方式,从未出现过问题。
6. 枚举单例:最安全的实现方式
java复制public enum EnumSingleton {
INSTANCE;
public void doSomething() {
// 业务方法
}
}
这是《Effective Java》作者Josh Bloch推荐的方式,也是目前公认最安全的单例实现。它能防止反射攻击和序列化问题,代码极其简洁。我在需要序列化的场景中特别偏爱这种方式。
7. 单例模式的高级话题
7.1 防止反射攻击
除了枚举方式,其他实现都可能通过反射创建新实例。我们可以修改构造器来防御:
java复制private Singleton() {
if (instance != null) {
throw new IllegalStateException("单例实例已存在");
}
}
7.2 处理序列化问题
如果单例需要序列化,必须实现readResolve方法:
java复制protected Object readResolve() {
return getInstance();
}
7.3 多类加载器环境
在OSGi或自定义类加载器环境中,同一个类可能被不同加载器加载,导致单例失效。解决方案可以是:
- 指定类加载器
- 使用上下文类加载器
- 在框架层面保证
8. 单例模式的替代方案
虽然单例模式很常用,但近年来也受到了一些批评。在某些场景下,可以考虑以下替代方案:
- 依赖注入:通过框架(如Spring)管理单例
- 静态工具类:对于无状态的工具方法
- 工厂模式:需要灵活控制实例创建时
我在一个大型微服务项目中就将部分单例改为了Spring管理的Bean,大大提高了可测试性。
