0Pricing
Java Academy · 课时

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 反馈 — 无需本地设置。

此课程中的所有课时

  1. Singleton:线程安全的实现
  2. 工厂方法模式
  3. 面向产品族的抽象工厂
  4. 使用流式 API 的建造者模式
← 返回 Java Academy