Java单例模式深度剖析:Synchronized的“正确打开方式”与性能优化之路
** 本文将深入探讨Java单例模式的核心实现,并聚焦于如何使用synchronized关键字保证线程安全,我们将从最基础的实现开始,逐步分析其潜在问题,并最终引出性能更优的解决方案,帮助你在面试和实际开发中游刃有余。

引言:为什么单例模式如此重要?
在软件开发中,单例模式(Singleton Pattern)是最简单、最常用的设计模式之一,它的核心目标是确保一个类只有一个实例,并提供一个全局访问点来访问这个实例。
想象一下:
- 配置管理器: 整个应用程序只需要一份配置信息。
- 数据库连接池: 避免频繁创建和销毁连接带来的性能开销。
- 日志记录器: 保证所有日志都写入同一个文件或控制台。
如果这些对象被多次实例化,不仅会浪费资源,还可能导致数据不一致的严重问题,掌握如何正确、高效地实现单例模式,是每一位Java程序员必备的核心技能,而synchronized,正是实现线程安全单例的“敲门砖”。
第一部分:初识单例模式与Synchronized
1 懒汉式:非线程安全的“雏形”
最直观的单例实现是“懒汉式”,即只有在第一次使用时才创建实例。

public class LazySingleton {
private static LazySingleton instance;
// 私有构造函数,防止外部 new
private LazySingleton() {}
// 全局访问点
public static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton(); // 第一次调用时创建
}
return instance;
}
}
问题所在:
在多线程环境下,如果两个线程同时调用getInstance()方法,并且都通过了instance == null的判断,那么就会创建两个LazySingleton实例,这严重破坏了单例的原则,是线程不安全的。
2 懒汉式升级:Synchronized的“第一次”尝试
为了解决线程安全问题,最直接的方法就是给getInstance()方法加上synchronized关键字,确保同一时间只有一个线程能进入该方法。
public class SynchronizedSingleton {
private static SynchronizedSingleton instance;
private SynchronizedSingleton() {}
// 使用 synchronized 关键字保证线程安全
public static synchronized SynchronizedSingleton getInstance() {
if (instance == null) {
instance = new SynchronizedSingleton();
}
return instance;
}
}
优点:
- 线程安全:
synchronized确保了在任何时刻,只有一个线程可以执行getInstance()方法,完美解决了多线程下创建多个实例的问题。
致命缺点:

- 性能低下: 这是
synchronized在方法级别上锁的典型问题,即使实例已经被创建,后续所有线程在调用getInstance()时,都必须在方法入口处等待锁,这导致每次获取实例都变成了一个同步操作,极大地影响了性能,尤其是在高并发场景下。
第二部分:Synchronized的“精妙”之处与优化
既然直接锁整个方法性能太差,我们能不能只锁“创建实例”这一行代码呢?这就引出了我们优化的核心方向。
1 双重检查锁定:DCL(Double-Checked Locking)
“双重检查锁定”模式是synchronized应用的一个经典案例,它旨在既保证线程安全,又避免不必要的同步开销。
public class DCLSingleton {
// volatile 是关键,防止指令重排序
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;
}
}
代码解析:
-
第一次检查 (
if (instance == null)):- 在
synchronized块之外进行,当实例已经被创建后,所有线程都可以直接获取到instance,而无需进入同步块,大大提升了性能。
- 在
-
synchronized块:- 只有当
instance为null时,线程才会尝试获取锁,这避免了每次调用都同步的问题。
- 只有当
-
第二次检查 (
if (instance == null)):- 在
synchronized块内部再次检查,这是为了防止在等待锁的过程中,另一个线程已经创建了实例,如果没有第二次检查,当线程A获取锁后,可能会重复创建实例。
- 在
2 为什么必须使用 volatile?
这是DCL模式中最容易被忽略,但至关重要的一点。instance = new DCLSingleton(); 这行代码在JVM中并非一个原子操作,大致可以分解为三步:
- 分配对象的内存空间。
- 初始化对象。
- 将
instance引用指向分配好的内存地址。
如果没有volatile,由于指令重排序,执行顺序可能变成 1 -> 3 -> 2,这就导致了灾难性的后果:
- 线程A 进入
synchronized块,执行了步骤1和3,但还没来得及执行步骤2(初始化)。 instance已经不为null了。- 线程B 调用
getInstance(),第一次检查instance != null,直接返回了一个未初始化完成的对象,如果此时线程B访问这个对象的任何字段,都可能导致NullPointerException或其他不可预知的错误。
volatile 关键字有两个作用:
- 保证可见性: 一个线程修改了
volatile变量,新值会立刻同步到主内存,并且其他线程读取时会从主内存读取,保证了线程间的可见性。 - 禁止指令重排序: 它会插入一个“内存屏障”,确保
instance = new DCLSingleton()的三个步骤不会被重排序,必须按 1->2->3 的顺序执行。
第三部分:超越Synchronized——更优雅的单例实现
虽然DCL模式已经很优秀,但在Java 5之后,我们有了更简洁、更可靠的实现方式。
1 饿汉式:简单但不够灵活
public class EagerSingleton {
// 类加载时就创建实例,由JVM保证线程安全
private static final EagerSingleton instance = new EagerSingleton();
private EagerSingleton() {}
public static EagerSingleton getInstance() {
return instance;
}
}
优点:
- 实现简单: 代码量少,没有锁。
- 线程安全: 实例由JVM在类加载阶段创建,
static变量在JVM中是原子操作,天然线程安全。
缺点:
- 启动慢: 无论是否使用,类加载时就会创建实例,可能造成资源浪费。
- 不灵活: 无法传递参数来决定如何创建实例。
2 静态内部类(推荐):完美的解决方案
这种方式被称为“ Initialization-on-demand holder idiom”,是《Effective Java》中推荐的单例模式。
public class HolderSingleton {
// 私有构造函数
private HolderSingleton() {}
// 静态内部类
private static class SingletonHolder {
// 静态初始化器,由JVM保证线程安全
private static final HolderSingleton INSTANCE = new HolderSingleton();
}
// 全局访问点
public static HolderSingleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
工作原理:
HolderSingleton类加载时,并不会立即加载SingletonHolder内部类。- 只有当第一次调用
getInstance()方法时,JVM才会去加载SingletonHolder类。 - 在加载
SingletonHolder类时,其静态变量INSTANCE会被初始化,从而创建HolderSingleton的唯一实例。 - 由于类的加载和静态变量的初始化过程由JVM保证线程安全,因此这种方式既保证了线程安全,又实现了延迟加载,还避免了
synchronized带来的性能开销。
优点:
- 线程安全: 由JVM保证。
- 延迟加载: 真正实现了按需创建。
- 高性能: 没有使用任何锁,获取实例的效率最高。
总结与对比
| 实现方式 | 线程安全 | 延迟加载 | 性能 | 备注 |
|---|---|---|---|---|
| **懒汉式(无锁) |
