首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >设计模式-单例模式

设计模式-单例模式

原创
作者头像
Swing Dunn
发布2025-12-04 15:23:44
发布2025-12-04 15:23:44
1750
举报
文章被收录于专栏:Qt_5_FQt_5_F

单例模式:保证内存中只存在一个该类的实例

单例的核心结构: 私有的构造函数,保证只能自己内部初始化实例

一个静态私有的实例对象,占用内存中唯一的实例的位置,后续访问都是访问它

一个静态公有的访问器:public static Classname getInstance(); 唯一能访问到单例的对外的接口

1.饿汉式(类加载的时候进行单例的初始化)

类加载到内存中的时候,实例化一个类的单例;

简单使用,推荐,它是线程安全的,因为只有在加载的时候可以调用到构造函数

唯一缺点: 不管用到预购,类加载的时候都会完成该单例的实例化。不用也会占用内存。

头文件:

代码语言:txt
复制
#pragma once
class Simple_SingleInstance
{
private :
	Simple_SingleInstance();
	static Simple_SingleInstance *instance;;

public:
	static Simple_SingleInstance* getInstance();
};

源文件:

代码语言:txt
复制
#include "Simple_SingleInstance.h"

Simple_SingleInstance* Simple_SingleInstance::instance = new Simple_SingleInstance();

Simple_SingleInstance :: Simple_SingleInstance()
{

}

Simple_SingleInstance*  Simple_SingleInstance::getInstance()
{
	return instance;
}

主函数:

代码语言:txt
复制
#include<iostream>
#include"Simple_SingleInstance.h"

int main()
{
    std::cout << "Hello World!" << std::endl;

    Simple_SingleInstance* p1 = Simple_SingleInstance::getInstance();
    Simple_SingleInstance * p2 = Simple_SingleInstance::getInstance();
    if (p1 == p2)
    {
        std::cout << "单例模式成功" << std::endl;
    }
    return 0;
}

运行结果:

在主函数执行之前,会先进入这里,类加载的时候,就初始化了单例的对象

2.懒汉式(第一次用的时候进行单例的初始化)

2.1简单的懒汉式

在第一次使用的时候进行单例的初始化;可能带来线程的安全问题。

头文件:

代码语言:txt
复制
#pragma once
class SingleInstance2
{
private:
	SingleInstance2();
	static SingleInstance2 * m_pInstance;

public:
	static SingleInstance2* GetInstance();
};  

源文件:

代码语言:txt
复制
#include "SingleInstance2.h"

SingleInstance2* SingleInstance2::m_pInstance = nullptr;


SingleInstance2::SingleInstance2()
{
}

SingleInstance2* SingleInstance2::GetInstance()
{
    //有两个线程同时访问的时候,在一个线程通过了这个为空的判断,但还没来得及实例出对象,另一个线程也进入
    //此处的判断,从而先后实例化出两个对象,破坏了单例的唯一性,在两个线程中使用的是两个不同的对象。
    if(m_pInstance == nullptr)
        m_pInstance = new SingleInstance2();
    return m_pInstance;
}

主函数和运行结果:

代码语言:txt
复制
#include<iostream>
#include"SingleInstance2.h"
#include<vector>
#include<thread>

void threadFunction(int id)
{
    SingleInstance2 * instance = SingleInstance2::GetInstance();
    std::cout << "Thread " << id << " got instance at address: "
        << instance << std::endl;
}

int main()
{
    const int NUN_THREADS = 100;

    std::vector<std::thread> threads;
    for (int i = 0; i < NUN_THREADS; i++)
    {
        // 使用 push_back - 需要先创建对象再移动/拷贝
       // threads.push_back(std::thread(threadFunction, i));

        // 使用 emplace_back - 直接在容器内构造对象
        threads.emplace_back(threadFunction, i);

     /*   •	性能更好: 避免不必要的拷贝或移动操作
        •	代码更简洁: 参数直接传递给构造函数
        •	避免临时对象: 直接在容器分配的内存中构造 */  
    }

    return 0;
}

运行结果: 你会发现 多线程创建的访问的instance的地址是不同的

解决懒汉式单例线程安全性的方法:

先不说C++的实现方法,先说说思路,加锁(参考java)

1.加锁的位置:对方法加锁,这种方法虽然达到了线程安全的目的,但是多个线程要排队等取锁才能进入这个函数,效率就降低了,所以不是很推荐

代码语言:txt
复制
    public static synchronized Singleton getInstance() {
        if (null == INSTANCE) {
            try {
                //模拟延迟,多线程下同时进入此代码块。
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            INSTANCE = new Singleton();
        }
        return INSTANCE;
    }

2.加锁的位置,只对创建的代码块加锁,但使用双重检查。

如果不适用双重检查带来的问题是:第一个线程通过为空检查后,第二个也进入了为空检查,第二个线程拿到锁,创建了对象,释放锁给第一个线程,第一个线程拿到锁还是会在此进入代码块创建一个新对象。使用双重检查可以避免你在上锁期间有人插队创建对象,你上锁之后再次检查对象是否为空。

双重检查锁虽然代码比较复杂,但是相对来说是比较完美的。

代码语言:txt
复制
public static Singleton getInstance() {
        if (null == INSTANCE) {
            synchronized (Singleton.class) {
                if (null == INSTANCE) {
                    try {
                        //模拟延迟,多线程下同时进入此代码块。
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    INSTANCE = new Singleton();
                }
            }

        }
        return INSTANCE;
    }

java中还有两种更好的写法:

私有的静态内部类 (类加载的时候不会加载内部静态类,在第一次调用的时候才会被加载)

枚举单例(枚举方法没有构造函数,所以不会被反序列化)

C++实现懒汉单例的方法:

1.静态局部变量的懒汉式单例(推荐, 线程安全)

头文件:

代码语言:txt
复制
#pragma once
class SingleInstance3
{
private:
	//使用默认构造函数和析构函数  禁止外部构造和外部析构
	SingleInstance3() = default;
	~SingleInstance3() = default;
	//禁止外部拷贝构造
	SingleInstance3(const SingleInstance3&) = delete;
    //禁止外部赋值运算符
	const SingleInstance3& operator = (const SingleInstance3&) = delete;

public:
	static SingleInstance3* getInstance();
};

源文件:

代码语言:txt
复制
#include "SingleInstance3.h"

SingleInstance3* SingleInstance3::getInstance()
{
    /**
    *
    *局部静态变量的方式实现单例
    *局部静态变量只在当前函数内部有效,其他函数无法访问
    *静态局部遍历只在第一次调用的时候初始化,存放在静态存储区,生命周期从第一次被创建到程序结束
    */
    static SingleInstance3 instance;
    return &instance;
}

2.使用unique_lock 和mutex实现双重锁

头文件:

代码语言:txt
复制
#pragma once
#include<mutex>
class SingleInstance4
{
private:
    //私有构造函数,防止外部实例化
    SingleInstance4() = default;
    //私有析构函数,防止外部删除
    ~SingleInstance4() = default;
    //拷贝构造函数和赋值构造设为私有,防止外部拷贝赋值
    SingleInstance4(const SingleInstance4&) =delete;
    const SingleInstance4& operator = (const SingleInstance4&) = delete;

private:
    static SingleInstance4* m_pInstance;
    static std::mutex m_mutex;

public: 
    static SingleInstance4* getInstance();

/**
 * 如果使用智能指针, m_pInstance应定义为 
 * std::shared_ptr<SingleInstance4> m_pInstance;
 static std::shared_ptr<SingleInstance4> getInstance();
 */
};

源文件:

代码语言:txt
复制
#include "SingleInstance4.h"

// 静态成员变量定义 - 关键步骤!
SingleInstance4* SingleInstance4::m_pInstance = nullptr;
std::mutex SingleInstance4::m_mutex ;
SingleInstance4* SingleInstance4::getInstance()
{
    // 第一次检查(不加锁) 可以使大部分线程跳过下面的过程 节约时间
    if (m_pInstance == nullptr)
    {
        // 使用 unique_lock 加锁
        std::unique_lock<std::mutex> lock(m_mutex);
        if (m_pInstance == nullptr)
        {
            volatile auto temp = new (std::nothrow)SingleInstance4();
            m_pInstance = temp;
        }
    }
    return m_pInstance;
}

3.使用std::call_once实现懒汉单例(线程安全)

头文件:

代码语言:txt
复制
#pragma once
#include<mutex>
class SingleInstance4
{
private:
    //私有构造函数,防止外部实例化
    SingleInstance4() = default;
    //私有析构函数,防止外部删除
    ~SingleInstance4() = default;
    //拷贝构造函数和赋值构造设为私有,防止外部拷贝赋值
    SingleInstance4(const SingleInstance4&) =delete;
    const SingleInstance4& operator = (const SingleInstance4&) = delete;

private:
    static SingleInstance4* m_pInstance;
    static std::once_flag single_once_flag;

public: 
    static SingleInstance4* getInstance();

/**
 * 如果使用智能指针, m_pInstance应定义为 
 * std::shared_ptr<SingleInstance4> m_pInstance;
 static std::shared_ptr<SingleInstance4> getInstance();
 */
};

源文件:

代码语言:txt
复制
#include "SingleInstance4.h"

// 静态成员变量定义 - 关键步骤!
SingleInstance4* SingleInstance4::m_pInstance = nullptr;
std::once_flag SingleInstance4::single_once_flag;

SingleInstance4* SingleInstance4::getInstance()
{
    // 第一次检查(不加锁) 可以使大部分线程跳过下面的过程 节约时间
    if (m_pInstance == nullptr)
    {
        // 使用call_once(falg,[&](){ 原子操作 }
        std::call_once(single_once_flag, [&]() {
            // 使用 nothrow:内存分配失败时返回 nullptr
            m_pInstance = new (std::nothrow)SingleInstance4();
            });
    }
    return m_pInstance;
}

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 单例模式:保证内存中只存在一个该类的实例
    • 1.饿汉式(类加载的时候进行单例的初始化)
    • 2.懒汉式(第一次用的时候进行单例的初始化)
      • 2.1简单的懒汉式
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档