题目
如何实现线程安全的单例模式?请用双重检查锁定实现并说明原理
信息
- 类型:问答
- 难度:⭐⭐
考点
单例模式,线程安全,volatile关键字,类加载机制,双重检查锁定
快速回答
实现线程安全单例模式的核心要点:
- 使用
private static volatile修饰实例变量 - 私有化构造方法
- 通过双重检查锁定(Double-Checked Locking)创建实例
- 使用
volatile防止指令重排序
示例代码:
public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
## 解析
1. 单例模式核心原理
单例模式确保一个类只有一个实例,并提供全局访问点。关键实现:
- 私有静态变量保存唯一实例
- 私有构造方法阻止外部实例化
- 公有静态方法提供访问入口
2. 线程安全实现方案对比
| 方案 | 优点 | 缺点 |
|---|---|---|
| 饿汉式 | 实现简单,线程安全 | 类加载时即创建实例,可能造成资源浪费 |
| 同步方法 | 线程安全 | 每次访问都同步,性能差 |
| 双重检查锁定 | 延迟加载,性能优化 | 实现稍复杂,需注意指令重排序问题 |
3. 双重检查锁定实现详解
public class Singleton {
// volatile 保证可见性和禁止指令重排序
private static volatile Singleton instance;
// 私有构造器
private Singleton() {
// 防止反射破坏单例
if (instance != null) {
throw new RuntimeException("Use getInstance() method");
}
}
public static Singleton getInstance() {
// 第一次检查:避免不必要的同步
if (instance == null) {
synchronized (Singleton.class) {
// 第二次检查:确保只有一个线程创建实例
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}关键点说明:
- volatile作用:防止JVM指令重排序。对象创建包含:1.分配内存 2.初始化对象 3.指向引用。若无volatile,步骤2和3可能颠倒,导致其他线程获取未初始化的实例。
- 双重检查必要性:外层检查避免每次调用都同步,内层检查防止多次实例化。
4. 最佳实践与注意事项
- 防止反射攻击:在构造器中检查实例是否存在(如上代码所示)
- 序列化安全:实现
readResolve()方法返回现有实例private Object readResolve() { return getInstance(); } - 枚举实现:更简洁的线程安全实现(Java 5+)
public enum Singleton { INSTANCE; // 添加业务方法 }
5. 常见错误
- 忘记
volatile关键字,导致指令重排序问题 - 同步块使用错误锁对象(应使用类对象锁)
- 未进行双重检查,直接在同步块外创建实例
- 忽略序列化和反射攻击的防护
6. 扩展知识
- 类加载机制:静态内部类实现(Holder模式)利用类加载的线程安全性
public class Singleton { private Singleton() {} private static class Holder { static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance() { return Holder.INSTANCE; } } - 应用场景:配置管理、线程池、数据库连接池等需要全局唯一访问点的资源
- 现代替代方案:依赖注入框架(如Spring)管理的单例Bean