单例模式有哪几种实现?如何保证线程安全?

单例模式有哪几种实现?如何保证线程安全?

重要内容

单例模式实现方式

常见有饿汉式、懒汉式、静态内部类、枚举单例等实现方式

如何保证线程安全?

推荐枚举单例、静态内部类或双重检查锁定 + 配合 volatile 修饰符

扩展知识

概况图

  • 饿汉式:简单,线程安全,但会在程序启动时就创建实例,可能浪费资源
  • 懒汉式:延迟加载,但需要注意线程安全问题(建议使用双重检查锁)
  • 静态内部类:线程安全,延迟加载,推荐使用
  • 枚举式:最简洁,线程安全,避免反序列化和反射攻击

饿汉式

原理:类加载时立即初始化实例,借助 JVM 类加载机制的线程安全性保障单例创建

  • 构造器私有化 => 防止new对象
  • 类的内部创建对象(该对象是static)
  • 提供一个静态的公共方法 getInstance
java 复制代码
public class EagerSingleton {
    // 类加载时立即初始化实例
    private static final EagerSingleton instance = new EagerSingleton();
    // 构造器
    private EagerSingleton() {}
    public static EagerSingleton getInstance() {
        return instance;
    }
}

优缺点

  • ✅ 绝对安全(JVM保证类加载过程同步),实现简单,无锁性能高
  • ❌ 未实现懒加载,可能造成资源浪费(实例未使用仍被创建)

懒汉式

原理 :在第一次调用 getInstance() 方法时才创建实例

基础版本(线程不安全)
java 复制代码
public class UnsafeLazySingleton {
    private static UnsafeLazySingleton instance;
    private UnsafeLazySingleton() {}
    public static UnsafeLazySingleton getInstance() {
        if (instance == null) {
            instance = new UnsafeLazySingleton(); // 多线程下可能创建多个实例
        }
        return instance;
    }
}

存在问题:线程不安全。在多线程环境下,可能会有多个线程同时创建实例,导致实例被创建多次

同步锁版本(线程安全,但是造成过多同步开销)
java 复制代码
public class SyncMethodLazySingleton {
    private static SyncMethodLazySingleton instance;
    private SyncMethodLazySingleton() {}
    // 使用 synchronized 来锁住该方法,但是会导致每次调用该方法都要进行同步操作
    public static synchronized SyncMethodLazySingleton getInstance() {
        if (instance == null) {
            instance = new SyncMethodLazySingleton();
        }
        return instance;
    }
}

存在问题:每次调用 getInstance 方法都需要加锁,而实际上只需要在第一次创建实例时加锁(在高并发环境下,频繁获取单例对象的锁操作会显著降低性能)

双重检查锁

双重检查锁定通过减少加锁的范围,避免了每次获取实例时都加锁的问题,提升了性能

java 复制代码
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;
    }
}

为什么双重检查锁定需要 volatile 关键字?

用于防止指令重排序,从而确保双重检查锁定的正确性

创建单例对象是一个非原子操作,分为以下三步

  1. 分配内存空间
  2. 初始化对象
  3. 将对象的引用赋值给 instance

在没有 volatile 修饰的情况下,编译器和CPU可能会对这些步骤进行重排序。此时,另一个线程可能会在instance被赋值后,但对象尚未完成初始化时访问它,从而导致错误(确保对象的初始化过程对所有线程可见


静态内部类

原理:利用类加载机制延迟初始化,静态内部类在首次访问时加载

java 复制代码
public class HolderSingleton {
    private HolderSingleton() {}
    private static class Holder {
        private static final HolderSingleton INSTANCE = new HolderSingleton();
    }
    public static HolderSingleton getInstance() {
        return Holder.INSTANCE;
    }
}

优缺点

  • ✅ 无锁高性能
  • ✅ 天然支持懒加载(在类加载时不会立即创建实例)

枚举类

枚举式单例是最简洁、最安全的实现方式,利用 Java 枚举类的特性保证了线程安全以及防止反序列化创建新的实例

java 复制代码
public enum EnumSingleton {
    INSTANCE;
    public void doSomething() { ... }
}
相关推荐
码匠许师傅21 小时前
【设计模式精讲】15.享元模式(Flyweight)
c++·设计模式·享元模式·uml
事圆则缓21 小时前
Android 常用设计模式速查
android·设计模式
码匠许师傅1 天前
【设计模式精讲】14.外观模式(Facade)
c++·设计模式·uml·外观模式
evans在进步2 天前
Java 常用设计模式(二):装饰器、适配器、责任链、模板方法、策略与观察者
java·开发语言·设计模式
码匠许师傅2 天前
【设计模式精讲】13.装饰器模式(Decorator)
c++·设计模式·uml·装饰器模式
evans在进步3 天前
Java 常用设计模式入门:建造者、工厂、单例、外观与代理
java·python·设计模式
字节跳动开源3 天前
VisActor全新图可视化开源项目:VGraph
前端·设计模式·开源
码匠许师傅3 天前
【设计模式精讲】11.桥接模式(Bridge)
c++·设计模式·桥接模式·uml
学习星球4 天前
AI Agent 成本工程实战:从 OpenAI Codex 的 8 个“烧 Token“Bug 学起
人工智能·设计模式·微信小程序
AI人工智能+电脑小能手4 天前
大白话说Java设计模式-46-责任链模式(业务实战篇)
java·设计模式·责任链模式·风控校验·servlet filter·spring interceptor·链式处理