单例模式( 饿汉式与懒汉式 )

目录

一、单例模式核心思想

二、饿汉式单例:类加载即初始化

[2.1 C++ 实现](#2.1 C++ 实现)

[2.2 Java 实现](#2.2 Java 实现)

[2.3 饿汉式特点分析](#2.3 饿汉式特点分析)

三、懒汉式单例:延迟初始化,按需创建

[3.1 C++ 实现](#3.1 C++ 实现)

[3.2 Java 实现](#3.2 Java 实现)

[3.3 懒汉式的线程安全问题](#3.3 懒汉式的线程安全问题)

[3.3.1 C++ 线程安全方案(双检锁)](#3.3.1 C++ 线程安全方案(双检锁))

[3.3.2 Java 线程安全方案(静态内部类 / 枚举)](#3.3.2 Java 线程安全方案(静态内部类 / 枚举))

[3.4 懒汉式特点分析](#3.4 懒汉式特点分析)

[四、饿汉式 vs 懒汉式:核心对比](#四、饿汉式 vs 懒汉式:核心对比)

五、单例模式的最佳实践

六、总结


在软件开发中,单例模式(Singleton Pattern) 是创建型设计模式的核心之一,它保证一个类仅有一个实例,并提供一个全局访问点,避免重复创建对象带来的资源浪费,同时保证状态一致性。

本文将结合 C++ 和 Java 代码,深入解析单例模式的两种经典实现:饿汉式(Eager Initialization)懒汉式(Lazy Initialization),帮你彻底理解其原理、差异与工程实践。


一、单例模式核心思想

单例模式的核心目标只有两个:

  1. 控制实例数量 :确保一个类在运行期间最多只存在一个实例
  2. 全局访问:提供一个全局入口,让任何地方都能获取到这个唯一实例。

为了实现这两个目标,单例模式必须满足三个约束:

  • 构造函数私有化:禁止外部通过 new 直接创建实例。
  • 禁用拷贝构造与赋值运算符:防止实例被复制,破坏唯一性。
  • 提供静态访问方法:作为获取实例的唯一全局入口。

二、饿汉式单例:类加载即初始化

饿汉式的核心思想是 **"急切初始化"**------ 在类加载阶段就创建好唯一实例,不管后续是否会使用它,就像 "饿汉" 一样迫不及待。

2.1 C++ 实现

cpp 复制代码
#pragma once
#include <iostream>

// 饿汉模式
class Bully {
public:
    // 禁用拷贝构造和赋值运算符
    Bully(const Bully&) = delete;
    Bully& operator=(const Bully&) = delete;

    // 全局访问点:返回唯一实例的引用
    static Bully& get() {
        return person;
    }

private:
    // 私有构造函数:禁止外部实例化
    Bully() {}

    // 静态成员变量:类加载时初始化
    static Bully person;
};

// 类外初始化静态成员(必须),自动调用构造函数
Bully Bully::person;

2.2 Java 实现

java 复制代码
// 恶汉模式(饿汉式)
class SingletonBully {
    // 类加载时即创建实例,private 保证外部无法直接访问
    private static SingletonBully instance = new SingletonBully();

    // 私有构造函数
    private SingletonBully() {}

    // 全局访问点
    public static SingletonBully getInstance() {
        return instance;
    }
}

2.3 饿汉式特点分析

优点 缺点
✅ 实现简单,代码清晰 ❌ 类加载时即初始化,可能造成内存浪费(若实例从未使用)
✅ 天生线程安全(类加载由 JVM/C++ 运行时保证) ❌ 无法延迟加载,灵活性较低
✅ 无锁,性能极高 ❌ 不适合实例创建成本高、使用频率低的场景

三、懒汉式单例:延迟初始化,按需创建

懒汉式的核心思想是 **"延迟初始化"**------ 只有在第一次获取实例时才创建对象,就像 "懒汉" 一样,不到万不得已不干活。

3.1 C++ 实现

cpp 复制代码
#pragma once
#include <iostream>

// 懒汉模式
class Fed {
public:
    // 禁用拷贝构造和赋值运算符
    Fed(const Fed&) = delete;
    Fed& operator=(const Fed&) = delete;

    // 全局访问点:第一次调用时创建实例
    static Fed& get() {
        if (instance == nullptr) {
            instance = new Fed();
        }
        return *instance;
    }

private:
    // 私有构造函数
    Fed() {}

    // 静态指针:初始为 nullptr,延迟初始化
    static Fed* instance;
};

// 类外初始化静态指针
//static Fed* instance = nullptr;
Fed* Fed::instance = nullptr;

⚠️ 注意:static Fed* instance = nullptr; 多写了一个 static,正确写法是 Fed* Fed::instance = nullptr;

3.2 Java 实现

java 复制代码
// 懒汉模式
class SingletonFed {
    // 静态变量初始为 null,延迟初始化
    private static SingletonFed instance;

    // 私有构造函数
    private SingletonFed() {}

    // 全局访问点:第一次调用时创建实例
    public static SingletonFed getInstance() {
        if (instance == null) {
            instance = new SingletonFed();
        }
        return instance;
    }
}

3.3 懒汉式的线程安全问题

基础版懒汉式在单线程环境 下是安全的,但在多线程环境下会出现问题:

  • 多个线程同时进入 if (instance == null) 判断,可能导致多个实例被创建,破坏单例约束。
3.3.1 C++ 线程安全方案(双检锁)
cpp 复制代码
#include <mutex>

class Fed {
public:
    static Fed& get() {
        // 第一次检查:无锁,提高性能
        if (instance == nullptr) {
            // 加锁:保证只有一个线程能进入创建逻辑
            std::lock_guard<std::mutex> lock(mtx);
            // 第二次检查:防止多线程重复创建
            if (instance == nullptr) {
                instance = new Fed();
            }
        }
        return *instance;
    }

private:
    static Fed* instance;
    static std::mutex mtx; // 互斥锁
};

Fed* Fed::instance = nullptr;
std::mutex Fed::mtx;
3.3.2 Java 线程安全方案(静态内部类 / 枚举)
  • 静态内部类(推荐) :利用类加载机制实现延迟初始化与线程安全。

    java 复制代码
    class SingletonFed {
        private SingletonFed() {}
    
        // 静态内部类:只有在被调用时才会加载,延迟初始化
        private static class Holder {
            private static final SingletonFed INSTANCE = new SingletonFed();
        }
    
        public static SingletonFed getInstance() {
            return Holder.INSTANCE;
        }
    }
  • 枚举(最安全) :Java 枚举天然保证单例,且防止反射与序列化破坏单例。

    java 复制代码
    enum SingletonFed {
        INSTANCE;
    
        public void doSomething() {
            // 业务逻辑
        }
    }

3.4 懒汉式特点分析

优点 缺点
✅ 延迟加载:只有在第一次使用时才创建实例,节省内存 ❌ 基础版线程不安全,需要额外处理(加锁 / 静态内部类)
✅ 适合实例创建成本高、使用频率低的场景 ❌ 加锁版本会带来一定性能开销
✅ 灵活性高,可根据业务场景调整初始化时机 ❌ 实现复杂度略高于饿汉式

四、饿汉式 vs 懒汉式:核心对比

对比维度 饿汉式 懒汉式
实例创建时机 类加载阶段 第一次获取实例时
线程安全 天生安全 基础版不安全,需额外处理
内存占用 类加载时即占用,可能浪费 延迟占用,按需分配
性能 无锁,性能极高 加锁版本性能略低
实现复杂度 简单 略复杂(需处理线程安全)
适用场景 实例小、使用频繁 实例大、使用频率低

五、单例模式的最佳实践

  1. 优先考虑饿汉式:如果实例创建成本低、使用频繁,饿汉式是最简单高效的选择。

  2. 延迟加载用懒汉式:如果实例创建成本高(如连接池、大对象),且可能不会被使用,选择懒汉式。

  3. Java 推荐枚举 / 静态内部类:枚举是最安全的单例实现,静态内部类是延迟初始化的优雅方案。

  4. C++ 推荐 C++11 静态局部变量 :C++11 后,静态局部变量的初始化是线程安全的,可极简实现懒汉式:

    cpp 复制代码
    class Fed {
    public:
        static Fed& get() {
            static Fed instance; // 线程安全的延迟初始化
            return instance;
        }
    private:
        Fed() {}
    };
  5. 警惕单例的滥用:单例模式会引入全局状态,增加代码耦合度,不利于单元测试,需谨慎使用。


六、总结

单例模式是软件开发中最常用的设计模式之一,饿汉式懒汉式是其两种核心实现:

  • 饿汉式:以空间换时间,简单高效,线程安全。
  • 懒汉式:以时间换空间,延迟加载,需处理线程安全。

在实际开发中,应根据业务场景选择合适的实现方式,同时注意单例模式的局限性,避免过度使用。

相关推荐
_饭团2 小时前
C 语言内存函数全解析:从 memcpy 到 memcmp 的使用与模拟实现
c语言·开发语言·c++·学习·算法·面试·改行学it
cmd2 小时前
前端基础必看:JS 变量提升 & 函数提升完整解析
前端·javascript
小金鱼Y2 小时前
前端必看:this 不是玄学!5 大绑定规则帮你永久告别 this 困惑
前端·javascript·面试
~无忧花开~2 小时前
React组件与Props完全指南
开发语言·前端·react
2401_884563242 小时前
C++中的观察者模式实战
开发语言·c++·算法
lsx2024062 小时前
SQL MAX() 函数详解
开发语言
毕设源码-邱学长2 小时前
【开题答辩全过程】以 基于python的天气预测可视化系统为例,包含答辩的问题和答案
开发语言·python
椰猫子2 小时前
html、css入门
开发语言·javascript·ecmascript