Singleton:线程安全的实现
使用双重检查锁定、枚举或持有者模式构建线程安全的 Singleton
Singleton:线程安全的实现 是 CoddyKit 上的免费 Java Academy 课时。 这是第 1 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Java Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Java Academy 课程共包含 4 节课。
单例模式
单例模式确保 JVM 中只存在一个类的实例。常见使用场景包括:配置管理器、连接池和日志记录服务。
朴素单例——不具备线程安全性
经典的延迟单例在并发访问下会失效:两个线程可能同时通过空值检查,从而创建出两个实例。
public class Config {
private static Config instance;
private Config() {}
public static Config getInstance() {
if (instance == null) { // race condition!
instance = new Config();
}
return instance;
}
}同步方法——简单但速度慢
在 getInstance() 上添加 synchronized 可以实现线程安全,但每次调用都会加锁,即使实例已经存在也是如此——这会产生不必要的开销。
public static synchronized Config getInstance() {
if (instance == null) instance = new Config();
return instance;
}双重检查锁定
先在无锁的情况下检查,然后在 synchronized 块中再次检查。字段必须使用 volatile,以防止指令重排。
public class Config {
private static volatile Config instance;
private Config() {}
public static Config getInstance() {
if (instance == null) {
synchronized (Config.class) {
if (instance == null) instance = new Config();
}
}
return instance;
}
}按需初始化持有者
这是最安全且最高效的延迟单例实现:由一个私有静态嵌套类持有实例。JVM 只会在首次调用 getInstance() 时加载该类——类加载本身具有线程安全性。
public class Config {
private Config() {}
private static final class Holder {
static final Config INSTANCE = new Config();
}
public static Config getInstance() {
return Holder.INSTANCE;
}
}枚举单例
《Effective Java》推荐使用只包含一个常量的枚举。它简洁、具备线程安全性和序列化安全性,并且不受反射攻击影响。
public enum AppConfig {
INSTANCE;
private String apiUrl = "https://api.example.com";
public String getApiUrl() { return apiUrl; }
}
// Usage:
String url = AppConfig.INSTANCE.getApiUrl();急切初始化
如果可以接受启动开销,请在类加载时使用 static final 字段初始化实例。这种方式简单且线程安全,但即使实例从未使用,也始终会创建它。
public class Config {
private static final Config INSTANCE = new Config();
private Config() {}
public static Config getInstance() { return INSTANCE; }
}单例与序列化
单例经过序列化再反序列化后会产生新实例,除非添加返回 INSTANCE 的 readResolve()。枚举方式会自动处理这一点。
private Object readResolve() { return INSTANCE; }单例与反射攻击
反射可以调用私有构造函数。可以在构造函数中检查实例是否已经存在,如果存在则抛出异常,以此进行防御。根据 JVM 规范,枚举单例不会受到此类攻击影响。
private Config() {
if (Holder.INSTANCE != null) {
throw new IllegalStateException("Use getInstance()");
}
}依赖注入框架中的单例
在 Spring 中,Bean 默认是单例(@Scope("singleton"))。您很少需要手动实现单例模式;请让 DI 容器管理它。
@Component // Spring manages a single instance
public class FeatureFlags {
private final boolean darkMode = true;
public boolean isDarkMode() { return darkMode; }
}测试单例
单例很难进行单元测试,因为它们携带全局状态。请优先使用依赖注入,而不是手动编写单例代码;在测试中通过构造函数注入传入模拟实例。
快速检查
《Effective Java》推荐哪种单例实现方式?
回顾
对于线程安全的单例,请使用按需初始化持有者或枚举方式。避免使用朴素的延迟初始化。在 Spring 应用中,请让框架处理作用域管理。
常见问题解答
「Singleton:线程安全的实现」课时是免费的吗?
是的 — 「Singleton:线程安全的实现」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Java Academy 课程的其余内容,请升级到 CoddyKit PRO。 Java Academy 课程共包含 4 节课。
「Singleton:线程安全的实现」这节课中我会学到什么?
使用双重检查锁定、枚举或持有者模式构建线程安全的 Singleton 你通过在浏览器中直接运行的动手代码来练习 Java Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Java Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Java Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 1 节课,共 4 节。
「Singleton:线程安全的实现」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Java Academy 课中编写并运行代码吗?
能。每节 Java Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。
此课程中的所有课时
- Singleton:线程安全的实现
- 工厂方法模式
- 面向产品族的抽象工厂
- 使用流式 API 的建造者模式