【设计模式-单例模式】

这篇具有很好参考价值的文章主要介绍了【设计模式-单例模式】。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

什么是单例模式?

在一个项目中的全局范围内, 一个类有且仅有一个实例对象。这个唯一的实例对象给其他模块提供数据的全局访问。这样的模式就叫单例模式
单例模式的典型例子就是任务队列。

那么如何去实现这样的一个单例模式的类?

首先, 考虑单例模式的要求为有且仅有一个实例对象。那么就先从构造函数入手。类的构造函数主要有:构造函数、拷贝构造函数、赋值运算符重载构造函数。

  • 对于构造函数,将构造函数的访问权限设为private , 这样就禁止了构造函数在类的外部被调用。而在类的内部保证只调用构造函数一次,这样就创建了类的唯一对象。又因为单例对象需要提供数据的全局访问,所以将这个唯一对象声明为 静态变量,静态变量的生命周期为从创建开始直到程序结束。一般将静态类的唯一对象设为private(数据隐藏,封装特性),而设计一个public静态函数提供访问对象的唯一接口。

关于类中的静态变量和静态函数的一些细节:

  • 类中静态变量和静态函数都属于类,而不属于任何类的对象。换句话说,一个类只有一个静态变量和静态函数,多个对象共用这一个。
  • 类的静态变量和静态函数在类中都可以直接访问,但是在类的外部必须在前面加上类名和作用域运算符::;
  • 类的静态变量在类的内部创建但是在类的外部初始化,一般在类的定义中初始化;而类的静态函数可以在类内也可以在类外初始化。 类外初始化别忘记上一点说的加上类名和::
  • 类的静态函数只能访问静态变量和静态函数,不能访问非静态变量和非静态函数。所以单例模型类中访问唯一对象的函数接口设为静态的。
  • 拷贝构造函数、赋值运算符重载构造函数给禁止掉或设为私有。这通过=delete 实现。

所以,单例模式的类示例代码如下:

// 定义一个单例模式的类
class Singleton
{
public:
    // = delete 代表函数禁用,
    Singleton(const Singleton& obj) = delete;//拷贝构造函禁止
    Singleton& operator=(const Singleton& obj) = delete;//赋值构造函数禁止
    static Singleton* getInstance();//静态函数提供访问唯一实例对象的唯一接口
private:
    Singleton() = default;//构造函数设为私有,并用default强调为系统默认的构造函数
    static Singleton* m_obj;//静态对象
};

饿汉模式和懒汉模式

根据实例对象被创建的时机分为饿汉模型懒汉模式

  • 饿汉模式
// 饿汉模式
class Singleton
{
public:
    // = delete 代表函数禁用,
    Singleton(const Singleton& obj) = delete;//拷贝构造函禁止
    Singleton& operator=(const Singleton& obj) = delete;//赋值构造函数禁止
    static Singleton* getInstance()//静态函数提供访问唯一实例对象的唯一接口
    {
    	return m_obj;
    }
private:
    Singleton() = default;//构造函数设为私有
    static Singleton* m_obj;//静态对象
};
// 静态成员初始化放到类外部处理
Singleton* Singleton::m_obj = new Singleton;

int main()
{
    Singleton* obj = Singleton::getInstance();
}

在饿汉模式下, 在类被加载时对象就被初始化。

  • 懒汉模式
class Singleton
{
public:
    // = delete 代表函数禁用,
    Singleton(const Singleton& obj) = delete;//拷贝构造函禁止
    Singleton& operator=(const Singleton& obj) = delete;//赋值构造函数禁止
    static Singleton* getInstance();//静态函数提供访问唯一实例对象的唯一接口
    { 
    	if(m_obj == NULL)
    	{
    		m_obj = new Singleton();
    		return m_obj;
    	}
    	else return m_obj;
    }
private:
    Singleton() = default;//构造函数设为私有
    static Singleton* m_obj;//静态对象
};
// 静态成员初始化放到类外部处理
Singleton* Singleton::m_obj=NULL;

int main()
{
    Singleton* obj = Singleton::getInstance();
}

懒汉模式下, 单例模式的对象在类加载不去创建,在被使用时才被创建。 所以懒汉模式是更省内存的。

线程安全问题

饿汉模式是没有线程安全问题的, 因为对象在类加载时就被创建出来。多个线程不能再创建新的对象,只能通过类提供的唯一接口访问对象。
懒汉模式会存在安全问题, 因为对象在使用时被创建。若多个线程同时使用的话可能就会创建多个对象。比如A线程第一次使用对象,使用if(m_obj == NULL)通过,下一步就是创建对象。而恰好此时时间片被线程B给占去,因为对象还为被创建,所以线程B也将开始创建对象,以此类推,所以这就可能创建多个对象,这就违背的单例模型的原则。
解决线程间数据同步的问题,最常用的办法是互斥锁。当一个线程将互斥锁锁上的时候,其他线程不能再次上锁,只能等该线程解锁后才能进行上锁和解锁的操作。换句话说, 同时只能有一个线程持有互斥锁。(一个坑位只能同时蹲一个人的意思 : )

  • 互斥锁
class Singleton//互斥锁的解决方法
{
public:
    // = delete 代表函数禁用,
    Singleton(const Singleton& obj) = delete;//拷贝构造函禁止
    Singleton& operator=(const Singleton& obj) = delete;//赋值构造函数禁止
    static Singleton* getInstance();//静态函数提供访问唯一实例对象的唯一接口
    { 
   		m_mutex.lock();//锁上,其他线程给我等着
    	if(m_obj == NULL)//互斥锁内的区域叫做临界区,该区域为原子操作,不能操作系统分段执行。
    	{
    		m_obj = new Singleton();
    		return m_obj;
    	}
    	else return m_obj;
    	m_mutex.unlock();//解锁,其他线程开始抢锁
    }
private:
    Singleton() = default;//构造函数设为私有
    static Singleton* m_obj;//静态对象
};
// 静态成员初始化放到类外部处理
Singleton* Singleton::m_obj=NULL;

int main()
{
    Singleton* obj = Singleton::getInstance();
}

由于互斥锁的存在,所以多线程的并发性会在临界区被狠狠的限制住(一次只能有一个线程操作,其他线程被阻塞),这大大降低了代码执行的效率。

  • 静态局部对象

互斥锁解决了线程安全的问题,但减低了代码执行的效率。但其实还有更好的方法可以解决线程安全的问题,那就是静态局部变量。

class Singleton
{
public:
    // = delete 代表函数禁用,
    Singleton(const Singleton& obj) = delete;//拷贝构造函禁止
    Singleton& operator=(const Singleton& obj) = delete;//赋值构造函数禁止
    static Singleton* getInstance();//静态函数提供访问唯一实例对象的唯一接口
    { 
    	static Singleton m_obj;
        return &m_obj;
    }
private:
    Singleton() = default;//构造函数设为私有
    static Singleton* m_obj;//静态对象
};
// 静态成员初始化放到类外部处理
Singleton* Singleton::m_obj=NULL;

int main()
{
    Singleton* obj = Singleton::getInstance();
}

这定义了一个静态的局部单例对象,并且将这个对象作为了唯一的单例实例。使用这种方式之所以是线程安全的,是因为在 C++11 标准中有如下规定,并且这个操作是在编译时由编译器保证的:(站在巨人的肩膀上就是舒服)
如果指令逻辑进入一个未被初始化的声明变量,所有并发执行应当等待该变量完成初始化。

总结:

懒汉模式的缺点是在创建实例对象的时候有安全问题(可以用互斥锁或静态局部变量解决),但这样可以减少内存的浪费(如果用不到就不去申请内存了)。
饿汉模式则相反,在我们不需要这个实例对象的时候,它已经被创建出来,占用了一块内存,但它不会存在线程安全问题。

最后用单例模式实现一个任务队列

#include <iostream>
#include <queue>
#include <mutex>
#include <thread>
using namespace std;

class TaskQueue
{
public:
    // = delete 代表函数禁用, 也可以将其访问权限设置为私有
    TaskQueue(const TaskQueue& obj) = delete;
    TaskQueue& operator=(const TaskQueue& obj) = delete;
    static TaskQueue* getInstance()//获取任务队列单例的唯一接口
    {
        return &m_obj;
    }
    // 任务队列是否为空
    bool isEmpty()
    {
        lock_guard<mutex> locker(m_mutex);//c++11特性,相当于互斥锁的上锁和解锁
        bool flag = m_taskQ.empty();
        return flag;
    }
    // 添加任务
    void addTask(int data)
    {
        lock_guard<mutex> locker(m_mutex);
        m_taskQ.push(data);
    }
    // 取出一个任务
    int takeTask()
    {
        lock_guard<mutex> locker(m_mutex);
        if (!m_taskQ.empty())
        {
            int res = m_taskQ.front();
            m_taskQ.pop();
            return res;
        }
        return -1;
    }
    // 删除一个任务
    bool popTask()
    {
        lock_guard<mutex> locker(m_mutex);
        if (!m_taskQ.empty())
        {
            m_taskQ.pop();
            return true;
        }
        return false;
    }
private:
    TaskQueue() = default;
    static TaskQueue m_obj;//饿汉模式下的单例模式,类加载时就被创建,直到程序退出才清理
    queue<int> m_taskQ;//维护的任务队列
    mutex m_mutex;
};
TaskQueue TaskQueue::m_obj;

int main()
{
    thread t1([]() {//线程1不断地往任务队列尾部添加任务
        TaskQueue* taskQ = TaskQueue::getInstance();//获取类的唯一对象,该对象调用类通过的接口操作任务队列。
        for (int i = 0; i < 100; ++i)
        {
            taskQ->addTask(i + 100);
            cout << "+++push task: " << i+100 << ", threadID: " << this_thread::get_id() << endl;
            this_thread::sleep_for(chrono::milliseconds(500));
        }
    });
    thread t2([]() {//线程2不断从任务队列头部取出任务
        TaskQueue* taskQ = TaskQueue::getInstance();
        this_thread::sleep_for(chrono::milliseconds(100));//睡眠100ms,原子操作,不会被打断
        while (!taskQ->isEmpty())
        {
            int data = taskQ->takeTask();
            cout << "---take task: " << data << ", threadID: " 
                << this_thread::get_id() << endl;
            this_thread::sleep_for(chrono::seconds(1));
        }
    });
    t1.join();//线程1结束前阻塞在这,等线程结束后释放线程1的资源
    t2.join();
}

【设计模式-单例模式】,设计模式,设计模式,单例模式,c++文章来源地址https://www.toymoban.com/news/detail-549293.html

//懒汉模式
#include <iostream>
#include <queue>
#include <mutex>
#include <thread>
using namespace std;

class TaskQueue
{
public:
    // = delete 代表函数禁用, 也可以将其访问权限设置为私有
    TaskQueue(const TaskQueue& obj) = delete;
    TaskQueue& operator=(const TaskQueue& obj) = delete;
    static TaskQueue& getInstance()
    {
        static TaskQueue m_obj;
        return m_obj;
    }
    // 任务队列是否为空
    bool isEmpty()
    {
        lock_guard<mutex> locker(m_mutex);//c++11特性,相当于互斥锁的上锁和解锁
        bool flag = m_taskQ.empty();
        return flag;
    }
    // 添加任务
    void addTask(int data)
    {
        lock_guard<mutex> locker(m_mutex);
        m_taskQ.push(data);
    }
    // 取出一个任务
    int takeTask()
    {
        lock_guard<mutex> locker(m_mutex);
        if (!m_taskQ.empty())
        {
            int res = m_taskQ.front();
            m_taskQ.pop();
            return res;
        }
        return -1;
    }
    // 删除一个任务
    bool popTask()
    {
        lock_guard<mutex> locker(m_mutex);
        if (!m_taskQ.empty())
        {
            m_taskQ.pop();
            return true;
        }
        return false;
    }
private:
    TaskQueue() = default;
    //static TaskQueue m_obj;//饿汉模式
    queue<int> m_taskQ;
    mutex m_mutex;
};
//TaskQueue TaskQueue::m_obj;

int main()
{
    thread t1([]() {//线程1
        TaskQueue& taskQ = TaskQueue::getInstance();//获取单例任务队列
        for (int i = 0; i < 100; ++i)
        {
            taskQ.addTask(i + 100);
            cout << "+++push task: " << i+100 << ", threadID: " << this_thread::get_id() << endl;
            this_thread::sleep_for(chrono::milliseconds(500));
        }
    });
    thread t2([]() {//线程2
        TaskQueue& taskQ = TaskQueue::getInstance();
        this_thread::sleep_for(chrono::milliseconds(100));//睡眠100ms,原子操作,不会被打断
        while (!taskQ.isEmpty())
        {
            int data = taskQ.takeTask();
            cout << "---take task: " << data << ", threadID: " 
                << this_thread::get_id() << endl;
            this_thread::sleep_for(chrono::seconds(1));
        }
    });
    t1.join();//线程1结束前阻塞在这,等线程结束后释放线程1的资源
    t2.join();
}

到了这里,关于【设计模式-单例模式】的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 设计模式(单例模式)

            保证指定的类只有一个实例,不能创建出其他的实例                 1.1 代码展示                 1.2 Singleton类中instance对象的创建时机                 Singleton类中instance对象的创建时机:在Singleton类被jvm加载的时候创建,Singleton类会在第一次使用的时

    2024年02月15日
    浏览(51)
  • 设计模式-单例模式

          单例模式(Singleton Pattern)是设计模式中最简单且最常用的一种创建型模式,其目的是保证一个类在整个系统中只存在一个实例,并提供全局访问点来获取这个唯一实例。这种模式主要适用于那些需要频繁实例化然后又希望避免因为多次实例化而消耗过多资源或产生副

    2024年01月17日
    浏览(53)
  • 【设计模式-单例模式】

    在一个项目中的全局范围内, 一个类 有且仅有一个实例对象 。这个唯一的实例对象给其他模块提供数据的 全局访问 。这样的模式就叫 单例模式 。 单例模式的典型例子就是任务队列。 首先, 考虑单例模式的要求为有且仅有一个实例对象。那么就先从构造函数入手。类的构

    2024年02月13日
    浏览(59)
  • 设计模式 ~ 单例模式

    单例模式:指在确保一个类只有一个实例,创建之后缓存以便继续使用,并提供一个全局访问点来访问该实例; 前端对于单例模式不常用,但对于单例的思想无处不在; 如:弹窗、遮罩层、登录框、vuex redux 中的 store; 闭包: 模块化:

    2024年02月16日
    浏览(57)
  • 设计模式——单例模式

    确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。 即保证一个类只有一个实例,并且提供一个全局访问点 优点 单例对象在内存中只有一个实例,减少了内存的开支。尤其对于一个频繁创建、销毁的对象时,单例模式的优势就更明显。 减少系统的性能

    2024年02月16日
    浏览(63)
  • 设计模式-单例模式进阶

    在前面的文章(设计模式-单例模式)中,我们分别介绍了四种单例设计模式,包括 普通恶汉式单例、双重检查锁单例(DCL)、静态内部类单例以及枚举单例 。但是,这四种模式还有一些 问题 我们没有仔细分析,以至于我们无法深入分析他们的优点以及可能存在的问题,更无法确

    2024年02月16日
    浏览(38)
  • 设计模式 : 单例模式笔记

    一个类 只能创建一个对象 ,这样的类的设计模式就称为单例模式,该模式保证 系统中 该类 只能有一个实例 (并且 父子进程共享 ),一个很典型的单例类就是C++STL的内存池 C++单例模式的基本设计思路: 私有化 构造函数 ,删除默认的 拷贝构造函数 和 赋值运算符重载 防止对象被直

    2024年02月12日
    浏览(52)
  • 设计模式一(单例模式)

    主要思路:将构造方法私有化,并对外提供一个static的方法来创建对象 缺点:一开始就创建对象,占用系统资源 单线程下不会出现问题,但多线程会会有并发问题,main方法的测试结果: 会发生同一时间创建了多个对象,所以出现了DCL双重检索 可以实现延迟实例化,并且是

    2024年01月23日
    浏览(43)
  • 设计模式_单例模式

    保证该类只有一个实例( static私有变量 ),并提供一个访问的它的全部访问点( getInstance() 方法 ),该单例可以被所有程序模块共享. 1)此类不可被复制. 2)此类不可被公开构造. 也就是说,在c++中,它的 构造函数,拷贝构造函数,赋值函数 不能被公开调用. 个人理解,“懒汉式”,那一定很

    2024年02月12日
    浏览(43)
  • 设计模式篇---单例模式

    单例模式是结构最简单的设计模式,通过单例模式可以保证在整个系统中的一个类只有一个实例,从而节约系统资源。举个例子,比如windows电脑下的任务管理器只能打开一个,这个就是单例模式,如果不这样做,则会弹出多个窗口,但这些窗口显示的内容完全一样,浪费了资

    2024年02月10日
    浏览(30)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包