23种设计模式——单例模式(Singleton)详解

✅作者简介:大家好,我是 Meteors., 向往着更加简洁高效的代码写法与编程方式,持续分享Java技术内容。
🍎个人主页:Meteors.的博客
💞当前专栏: 设计模式

✨特色专栏: 知识分享
🥭本文内容: 23种设计模式------单例模式(Singleton)​详解
📚 ** ps ** : 阅读文章如果有问题或者疑惑,欢迎在评论区提问或指出。


目录

[一. 背景](#一. 背景)

[二. 单例模式介绍](#二. 单例模式介绍)

[三. 单例模式使用场景](#三. 单例模式使用场景)

[四. 单例模式实现方式](#四. 单例模式实现方式)

[方式一:饿汉式(Eager Initialization)](#方式一:饿汉式(Eager Initialization))

[方式二:懒汉式(Lazy Initialization)](#方式二:懒汉式(Lazy Initialization))

方式三:枚举(Enum)

[五. 各种实现方式对比](#五. 各种实现方式对比)


一. 背景

单例模式是项目中很常用的设计模式。在项目的配置管理器中经常使用,负责管理应用的全局配置信息。通过单例模式可以确保整个应用中只有一个配置管理器实例,统一管理所有配置。

二. 单例模式介绍

单例模式是一种​​创建型设计模式​​,它确保一个类只有一个实例,并提供一个全局访问点来获取这个实例。

它的核心思想是:​​控制实例的数量,节约系统资源​​。


三. 单例模式使用场景

在许多场景下,我们只需要一个对象来完成全局性的工作,创建多个实例不仅没有必要,还会浪费资源,甚至导致程序行为异常。例如:

  • ​配置信息类​​:整个应用共享一份配置,读取和修改都通过同一个对象进行。

  • ​日志记录类​​:所有日志都通过一个日志器写入同一个文件,避免多实例操作文件导致内容错乱。

  • ​数据库连接池​​:管理数据库连接,需要全局唯一以便高效管理连接资源。

  • ​线程池​​:类似数据库连接池。

  • ​缓存系统​​:如 Redis 客户端,通常一个应用一个实例就够了。

  • ​工具类​​:一些只提供静态方法,没有自身状态的工具类,也常被设计为单例。

如果不使用单例模式,而是随意创建实例,可能会导致:

  • ​资源浪费​​:频繁创建和销毁对象开销大。

  • ​数据不一致​​:多个实例可能持有不同的状态(例如,配置被一个实例修改,另一个实例却不知道)。

  • ​程序错误​​:例如多个日志实例同时写一个文件,会导致日志内容混乱。


四. 单例模式实现方式

实现一个单例模式通常需要注意以下三点:

  1. ​私有化构造方法​ ​:防止外部通过 new关键字创建实例。

  2. ​内部创建并持有该私有静态实例​​:在类内部自己创建这个唯一的实例。

  3. ​提供一个公共的静态方法​​:供外部获取这个唯一的实例。

根据实例创建的时机,主要分为两种模式:​​饿汉式​ ​和​​懒汉式​​。

方式一:饿汉式(Eager Initialization)

类加载时就直接初始化实例。​​简单、线程安全,但可能造成资源浪费​​(如果实例一直没被用到)。

复制代码
public class EagerSingleton {
    // 1. 在类加载时就创建好实例
    private static final EagerSingleton INSTANCE = new EagerSingleton();

    // 2. 私有化构造函数
    private EagerSingleton() {}

    // 3. 提供全局访问点
    public static EagerSingleton getInstance() {
        return INSTANCE;
    }
}

​优点​​:实现简单,线程安全(由 JVM 类加载机制保证)。

​缺点​​:如果实例很大且从未使用,会造成内存浪费。


方式二:懒汉式(Lazy Initialization)

延迟加载,只有在第一次被调用时才创建实例。

​a) 基础版本(线程不安全)​

多线程环境下可能创建多个实例。

复制代码
public class UnsafeLazySingleton {
    private static UnsafeLazySingleton instance;

    private UnsafeLazySingleton() {}

    public static UnsafeLazySingleton getInstance() {
        // 如果实例不存在,则创建
        if (instance == null) {
            instance = new UnsafeLazySingleton();
        }
        return instance;
    }
}

​b) 同步方法版(线程安全但效率低)​

通过 synchronized加锁保证线程安全,但每次获取实例都要同步,性能差。

复制代码
public class SynchronizedLazySingleton {
    private static SynchronizedLazySingleton instance;

    private SynchronizedLazySingleton() {}

    // 使用 synchronized 关键字修饰方法
    public static synchronized SynchronizedLazySingleton getInstance() {
        if (instance == null) {
            instance = new SynchronizedLazySingleton();
        }
        return instance;
    }
}

​c) 双重校验锁(DCL, Double-Checked Locking)​

​推荐写法​​。在加锁前后进行两次检查,兼顾线程安全和性能。

复制代码
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关键字在这里至关重要,它防止了 new DCLSingleton()这一步的指令重排序,避免了其他线程获取到一个未初始化完成的对象。

​d) 静态内部类(Holder Class)​

​最优雅、最推荐的实现方式之一​​。利用 JVM 的类加载机制保证线程安全,同时实现了懒加载。

复制代码
public class InnerClassSingleton {
    // 私有化构造方法
    private InnerClassSingleton() {}

    // 静态内部类持有实例
    private static class SingletonHolder {
        private static final InnerClassSingleton INSTANCE = new InnerClassSingleton();
    }

    // 调用 getInstance 时,才会加载 SingletonHolder 类,从而初始化 INSTANCE
    public static InnerClassSingleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

​优点​​:

  • ​懒加载​ ​:只有在调用 getInstance()时,内部类 SingletonHolder才会被加载,实例才会被创建。

  • ​线程安全​​:由 JVM 在类加载时完成初始化,天然线程安全。

  • ​实现简单​​:无需同步代码块,代码简洁。


方式三:枚举(Enum)

​《Effective Java》作者 Josh Bloch 强烈推荐的方式​​。它不仅能避免多线程同步问题,还能防止反序列化重新创建新的对象。

复制代码
public enum EnumSingleton {
    INSTANCE; // 唯一的实例

    // 可以添加任意方法
    public void doSomething() {
        System.out.println("Doing something by " + this.toString());
    }
}

// 使用方式
EnumSingleton.INSTANCE.doSomething();

​优点​​:

  • ​绝对防止多实例​​:由 JVM 从根本上保证。

  • ​防止反射攻击​​:枚举类不能通过反射创建实例。

  • ​防止反序列化​​:枚举类在反序列化时不会创建新对象。

  • ​代码极简​​。

​缺点​​:不够灵活(例如无法实现延迟初始化)。


五. 各种实现方式对比

实现方式 懒加载 线程安全 性能 防反射/反序列化 推荐度
​饿汉式​ ⭐⭐
​同步方法懒汉式​
​双重校验锁(DCL)​ ⭐⭐⭐⭐
​静态内部类​ ⭐⭐⭐⭐⭐
​枚举​ ⭐⭐⭐⭐⭐

​**​选择?:**​

  • 如果对内存不敏感,追求极致的简单,可以用​​饿汉式​​。

  • 如果需要懒加载,且是现代 Java 开发,​​静态内部类​​是最佳选择,简单又安全。

  • 如果需要防御高级的攻击(如反射、反序列化),或者实现一个表示状态的单例,​​枚举​​是最佳选择。

  • ​双重校验锁​​稍微复杂,但在一些特定场景(如需要延迟初始化且实例字段需要延迟初始化时)仍有其价值。

当然,单例模式有时也会限制之后相关类的异步实现,使用前要仔细考虑!


最后,

其它设计模式会陆续更新,希望文章对你有所帮助!

相关推荐
秋难降2 小时前
老板让我一天加 3 种支付方式,我用工厂模式躺赢了
java·设计模式
MasterNeverDown20 小时前
ASP.NET Core 中的构建者模式
设计模式·建造者模式
jiayi1 天前
从 0 到 1 带你打造一个工业级 TypeScript 状态机
前端·设计模式·状态机
yw00yw1 天前
常见的设计模式
开发语言·javascript·设计模式
我们从未走散1 天前
设计模式学习笔记-----抽象责任链模式
java·笔记·学习·设计模式·责任链模式
别再问我单例了1 天前
01-设计模式系列之---七大原则助你高效开发(完整版)
设计模式
哆啦code梦1 天前
设计模式之命令模式
设计模式·命令模式
快乐的划水a1 天前
中介者模式及优化
c++·设计模式·中介者模式
pengzhuofan1 天前
Java设计模式-代理模式
java·设计模式·代理模式