前言
之前在这篇 一文漫谈设计模式之创建型模式(一) 文章留了个坑位,现在来说完成下。
| 实现方式 | 线程安全 | 延迟加载 | 关键特点/原理 | 推荐指数 |
|---|---|---|---|---|
| 饿汉式 | ✅ 安全 | ❌ 不支持 | 类加载时即初始化实例。实现简单,但可能造成资源浪费。 | ⭐⭐⭐ |
| 懒汉式(基础版) | ❌ 不安全 | ✅ 支持 | 首次调用时创建实例。实现简单,但多线程下不安全。 | ⭐ |
| 懒汉式(同步方法) | ✅ 安全 | ✅ 支持 | 在getInstance方法上加synchronized锁。线程安全但效率低。 |
⭐⭐ |
| 双重校验锁(DCL) | ✅ 安全 | ✅ 支持 | 两次检查实例是否为null,配合volatile关键字避免指令重排序。 |
⭐⭐⭐⭐ |
| 静态内部类 | ✅ 安全 | ✅ 支持 | 利用类加载机制保证线程安全,由JVM在首次使用时加载内部类并初始化实例。 | ⭐⭐⭐⭐⭐ |
| 枚举 | ✅ 安全 | ❌ 不支持(本质饿汉式) | 写法简洁,由JVM保证实例唯一性,能防止反射和反序列化破坏单例。 | ⭐⭐⭐⭐⭐ |
案例
饿汉式 (Eager Initialization)
java
public class EagerSingleton {
// 类加载时就初始化实例
private static final EagerSingleton INSTANCE = new EagerSingleton();
// 私有构造器防止外部实例化
private EagerSingleton() {
System.out.println("EagerSingleton instance created!");
}
// 全局访问点
public static EagerSingleton getInstance() {
return INSTANCE;
}
public void doSomething() {
System.out.println("Doing something...");
}
}
懒汉式 - 基础版 (非线程安全)
java
public class LazySingleton {
// 初始化为null,需要时才创建
private static LazySingleton instance;
private LazySingleton() {
System.out.println("LazySingleton instance created!");
}
// 非线程安全的懒加载
public static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
public void doSomething() {
System.out.println("Doing something...");
}
}
懒汉式 - 同步方法版
java
public class SynchronizedLazySingleton {
private static SynchronizedLazySingleton instance;
private SynchronizedLazySingleton() {
System.out.println("SynchronizedLazySingleton instance created!");
}
// 在方法级别加synchronized保证线程安全
public static synchronized SynchronizedLazySingleton getInstance() {
if (instance == null) {
instance = new SynchronizedLazySingleton();
}
return instance;
}
public void doSomething() {
System.out.println("Doing something...");
}
}
双重校验锁 (DCL - Double Checked Locking)
java
public class DCLSingleton {
// volatile防止指令重排序
private static volatile DCLSingleton instance;
private DCLSingleton() {
System.out.println("DCLSingleton instance created!");
}
public static DCLSingleton getInstance() {
if (instance == null) { // 第一次检查,避免不必要的同步
synchronized (DCLSingleton.class) { // 同步块
if (instance == null) { // 第二次检查
instance = new DCLSingleton();
}
}
}
return instance;
}
public void doSomething() {
System.out.println("Doing something...");
}
}
关键点解析
(1) volatile关键字的作用
禁止指令重排序:instance = new Singleton()在JVM中分为三步:1分配内存空间;2初始化对象;3将实例指向内存地址。
若未使用volatile,JVM可能按1->3->2的顺序执行(指令重排序)。此时若线程A执行到步骤3(未初始化),线程B在第一次检查时可能直接返回未初始化的实例,导致错误。
保证可见性:确保多线程环境下,一个线程对instance的修改能立即被其他线程看到。
(2) 双重检查的必要性:
第一次检查:避免每次调用getInstance都进入同步块,提升性能。
第二次检查:防止多个线程同时通过第一次检查后,重复创建实例。例如,线程A和B均通过第一次检查,A先获取锁并创建实例后,B再次获取锁时需第二次检查以避免重复创建。
静态内部类 (Static Inner Class / Holder)
java
public class InnerClassSingleton {
private InnerClassSingleton() {
System.out.println("InnerClassSingleton instance created!");
}
// 静态内部类,只有在被引用时才会加载
private static class SingletonHolder {
private static final InnerClassSingleton INSTANCE = new InnerClassSingleton();
}
public static InnerClassSingleton getInstance() {
return SingletonHolder.INSTANCE; // 这里才会触发内部类的加载
}
public void doSomething() {
System.out.println("Doing something...");
}
}
枚举
java
public enum EnumSingleton {
INSTANCE; // 枚举实例,由JVM保证唯一性
// 可以添加任意方法
public void doSomething() {
System.out.println("EnumSingleton doing something...");
}
// 也可以有属性
private String data;
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
}
// 使用方式:
// EnumSingleton singleton = EnumSingleton.INSTANCE;
// singleton.doSomething();
测试代码
java
public class SingletonTest {
public static void main(String[] args) {
// 测试饿汉式
EagerSingleton eager1 = EagerSingleton.getInstance();
EagerSingleton eager2 = EagerSingleton.getInstance();
System.out.println("EagerSingleton same instance? " + (eager1 == eager2));
// 测试静态内部类
InnerClassSingleton inner1 = InnerClassSingleton.getInstance();
InnerClassSingleton inner2 = InnerClassSingleton.getInstance();
System.out.println("InnerClassSingleton same instance? " + (inner1 == inner2));
// 测试枚举
EnumSingleton enum1 = EnumSingleton.INSTANCE;
EnumSingleton enum2 = EnumSingleton.INSTANCE;
System.out.println("EnumSingleton same instance? " + (enum1 == enum2));
// 多线程测试
testWithMultipleThreads();
}
private static void testWithMultipleThreads() {
System.out.println("\nTesting with 10 threads:");
for (int i = 0; i < 10; i++) {
new Thread(() -> {
LazySingleton instance = LazySingleton.getInstance();
System.out.println("Thread " + Thread.currentThread().getId() +
" got instance: " + instance.hashCode());
}).start();
}
}
}
总结
关键要点说明:
饿汉式:最简单的实现,但可能在未使用时占用内存
基础懒汉式:在多线程环境下可能会创建多个实例
同步方法懒汉式:线程安全但性能差(每次调用都要同步)
双重校验锁:需要volatile关键字,适用于JDK 5+
静态内部类:优雅的延迟加载实现,推荐使用
枚举:最安全的实现,天然防止反射和序列化破坏
在实际开发中,如果不需要延迟加载,推荐使用枚举;如果需要延迟加载,推荐使用静态内部类。这两种方式都是《Effective Java》推荐的单例实现方式。