单例模式总结

单例模式本身是一种设计模式,是否线程安全取决于具体实现方式。以下是不同单例实现方式的线程安全性分析及优化方案:


一、线程不安全的单例实现

1. 懒汉式(未同步)
java 复制代码
public class Singleton {
    private static Singleton instance;

    private Singleton() {}

    public static Singleton getInstance() {
        if (instance == null) {                // 线程A和B可能同时进入此判断
            instance = new Singleton();        // 导致创建多个实例
        }
        return instance;
    }
}

问题 :多线程同时调用 getInstance() 会破坏单例性。


二、线程安全的单例实现

1. 饿汉式(线程安全)
java 复制代码
public class Singleton {
    private static final Singleton instance = new Singleton();

    private Singleton() {}

    public static Singleton getInstance() {
        return instance;  // 类加载时已初始化,JVM保证线程安全
    }
}

特点:

  • 优点:实现简单,无锁性能高
  • 缺点:无法延迟加载(即使未使用也会初始化)

2. 懒汉式(同步方法)
java 复制代码
public class Singleton {
    private static Singleton instance;

    private Singleton() {}

    public static synchronized Singleton getInstance() {  // 方法级同步
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

特点:

  • 优点:实现简单,保证线程安全
  • 缺点:每次调用都加锁,性能差(锁粒度太大)

3. 双重检查锁定(DCL,线程安全)
java 复制代码
public class Singleton {
    private static volatile Singleton instance;  // 必须用volatile

    private Singleton() {}

    public static Singleton getInstance() {
        if (instance == null) {                  // 第一次检查(无锁)
            synchronized (Singleton.class) {     // 加锁
                if (instance == null) {          // 第二次检查(有锁)
                    instance = new Singleton();  // volatile禁止指令重排序
                }
            }
        }
        return instance;
    }
}

关键点:

  • volatile 防止指令重排序(避免返回未初始化的对象)
  • 减少锁竞争(仅首次创建时同步)

4. 静态内部类(线程安全)
java 复制代码
public class Singleton {
    private Singleton() {}

    private static class Holder {
        static final Singleton INSTANCE = new Singleton();  // 类加载时初始化
    }

    public static Singleton getInstance() {
        return Holder.INSTANCE;  // 首次调用时加载Holder类
    }
}

特点:

  • 结合懒加载与无锁线程安全
  • JVM保证类加载过程的线程安全性

5. 枚举实现(最佳实践)
java 复制代码
public enum Singleton {
    INSTANCE;  // 枚举实例天然单例且线程安全

    public void doSomething() {
        // 业务方法
    }
}

优势:

  • 绝对防止反射攻击(枚举类无法通过反射创建实例)
  • 自动处理序列化与反序列化
  • 代码最简洁

三、线程安全单例的核心要点

实现方式 线程安全 延迟加载 性能 防反射 防序列化
饿汉式 ✅ ❌ ⭐⭐⭐⭐ ❌ ❌
同步懒汉式 ✅ ✅ ⭐ ❌ ❌
双重检查锁定 ✅ ✅ ⭐⭐⭐ ❌ ❌
静态内部类 ✅ ✅ ⭐⭐⭐⭐ ❌ ❌
枚举 ✅ ❌ ⭐⭐⭐⭐ ✅ ✅

四、常见面试陷阱

  1. 为什么DCL需要volatile?

    • 防止指令重排序:new Singleton() 的字节码分为三步(分配内存→初始化→引用赋值),不加volatile可能导致其他线程获取到未初始化的对象。
  2. 静态内部类如何保证线程安全?

    • JVM在类加载时(首次访问Holder.INSTANCE)会加锁(ClassLoader机制),保证初始化过程线程安全。
  3. 枚举单例为何能防反射?

    • 枚举类的构造方法在反射调用时会抛出IllegalArgumentException,源码中明确禁止反射创建枚举实例。

五、总结回答

"单例模式能否保证线程安全取决于具体实现方式:

  1. 线程不安全实现:如无同步的懒汉式
  2. 线程安全实现 :
    • 饿汉式(简单但无法延迟加载)
    • 双重检查锁定(需加volatile)
    • 静态内部类(兼顾性能与懒加载)
    • 枚举(最佳实践,防反射/序列化攻击)
      实际开发中推荐使用枚举或静态内部类实现单例模式。"
相关推荐
蛋蛋的就会顺顺的5 小时前
hot100 p0——栈,贪心算法,动态规划
java·前端·javascript·算法·leetcode·力扣
需要8266 小时前
Spring Bean 生命周期:从实例化到销毁的完整钩子
java·后端·spring
小 黄 鸡6 小时前
HashMap 面试必会知识点详解(JDK 8)
java·面试·职场和发展
死磕音视频7 小时前
让小模型做选择题:限定候选项并读取 logits 概率的入门实践(附 Python 代码)
开发语言·python·大模型·文本分类·transformers
伯恩bourne8 小时前
Spring Boot Bean 注册的两种方式:@Component 家族 vs @Configuration + @Bean
java·spring boot·后端
silvamux_8 小时前
curl 测试 OpenAI 兼容 API 的完整流程
java·人工智能·缓存·ai·oneapi
省钱兄--zs9 小时前
深圳24小时自助健身房软硬件解决方案实战指南与开发流程详解
java·数据仓库·spring boot·小程序·架构
顽疲9 小时前
Java vue 养老系统源码详解:月账单 care_month_bill 与 Quartz 扣费 Job 实践
java·开发语言·vue.js·vue养老·springboot养老·java养老
Wang's Blog9 小时前
Java 项目实战: 交友项目 - 安装开发工具与环境准备
java·开发语言·交友
用户21296835261689 小时前
开源了一个 Java 多智能体 LLM 框架 Baton:一个依赖,把 Agent 循环、工具调用、记忆、编排全配齐
java·llm