宁波网站建设方案推广,大型网站seo方法,免费行情软件app网站下载大全安卓,中国设计网站排名单例模式 OVERVIOW 单例模式1.单例模式实现2.饿汉与懒汉#xff08;1#xff09;饿汉模式#xff08;2#xff09;懒汉模式 3.懒汉线程安全1#xff08;1#xff09;引入互斥锁#xff08;2#xff09;引入双重检查锁定#xff08;3#xff09;引入原子变量 4.懒汉线…单例模式 OVERVIOW 单例模式1.单例模式实现2.饿汉与懒汉1饿汉模式2懒汉模式 3.懒汉线程安全11引入互斥锁2引入双重检查锁定3引入原子变量 4.懒汉线程安全21设置局部静态对象 5.简单案例运用1任务队列简单实现2用户登录 项目全局范围内某个类的实例有且仅有一个通过这个实例向其他模块提供数据的全局访问这种模式就叫单例模式。
单例模式的典型应用就是任务队列。使用单例模式来替代全局变量对全局变量进行管理直接使用全局变量会破坏类的封装全局变量随意读写通过单例模式的类提供的成员函数进行访问。
单例模式优点
提高性能避免频繁的创建销毁对象提高性能节省内存空间在内存中只有一个对象节省内存空间避免多重占用避免对共享资源的多重占用全局访问可全局访问利用单例模式避免全局变量的出现
单例模式缺点 扩展困难单例模式中没有抽象层因此扩展困难 不适用于变化的对象如果同类型的对象总是要在不同的用例场景发生变化单例就会引起数据错误不能保存状态。 职责过重违背了单一职责原则 负面问题 为了节省资源将数据库连接池对象设计为单例类可能会导致共享连接池对象的程序过多而出现连接池溢出。 如果实例化的单例对象长时间不被利用系统会认为是垃圾而被回收这将导致对象状态的丢失。
单利模式使用场景
需要频繁实例化然后销毁的对象创建对象耗时过多or消耗资源过多但又经常使用到的对象有状态的工具类对象频繁访问数据库或文件的对象要求只有一个对象的场景
1.单例模式实现
如果使用单例模式首先要保证这个类的实例有且仅有一个。因此就必须采取一些操作涉及一个类多对象操作的函数有以下几个
构造函数创建一个新的对象拷贝构造函数根据已有对象拷贝出一个新的对象拷贝赋值操作符重载函数两个对象之间的赋值
为了把一个类可以实例化多个对象的路堵死需要对以上几个函数做如下处理
构造函数私有化在类内部只调用一次这是可控的。 由于类外部不能使用构造函数所以在类内部创建的唯一的对象必须是静态的这样就可以通过类名来访问了为了不破坏类的封装把这个静态对象设置为私有。在类中只有它的静态成员函数才能访问其静态成员变量所以给这个单例类提供一个静态函数用于得到这个静态的单例对象。 拷贝构造函数私有化或者禁用使用 delete拷贝赋值操作符重载函数私有化或者禁用从单例的语义上讲该函数已经毫无意义所以在类中不再提供这样一个函数故将它也一并处理
单例模式就是给类创建一个唯一的实例对象UML类图如下 #includeiostream
using namespace std;
/*1.关于类创建后的默认提供的函数- 在创建一个新的类之后 会默认提供3个构造函数 1个析构函数- 2个操作符重载移动赋值操作符重载、拷贝赋值操作符重载移动构造函数 拷贝构造函数2.关于单例模式下类的实例化- 在通过将 无参构造函数、拷贝构造函数、拷贝赋值操作符重载函数禁用之后 TaskQueue类已经无法在外部创建任何的对象- 要得到TaskQueue的实例无法通过new操作符得到 只能通过类名得到需要将对象设置为静态对象- 通过类名访问类内部的属性和方法 其属性和方法一定是静态的若不是静态需要通过对象来调用- 能够操作静态成员变量的函数 只有静态成员函数
*///单例模式任务队列
class TaskQueue {
public://无参构造函数//TaskQueue() delete;//拷贝构造函数TaskQueue(const TaskQueue t) delete;//赋值操作符重载函数TaskQueue operator(const TaskQueue t) delete;// delete 代表函数禁用, 也可以将其访问权限设置为私有//静态成员公共函数用于获取实例static TaskQueue *getInstance() { return m_taskq; }void printTest() { cout i am a public method of a singleton class endl; }private://无参构造函数TaskQueue() default;//拷贝构造函数//TaskQueue(const TaskQueue t) default;//赋值操作符重载函数//TaskQueue operator(const TaskQueue t) default;//通过类名访问静态属性或方法来创建类实例需要在类外部做初始化处理static TaskQueue *m_taskq;
};//静态成员初始化放到类外部处理
TaskQueue* TaskQueue::m_taskq new TaskQueue;int main() {//获取TaskQueue的单例对象 由m_taskq指针指向TaskQueue* m_taskq TaskQueue::getInstance();//由m_taskq指针调用单例类内部的成员方法m_taskq-printTest();return 0;
}以上为单例模式中的饿汉模式在定义单例类的时候就将类对应的单例对象一并创建出来了。
2.饿汉与懒汉
在实现一个单例模式的类的时候有两种处理模式
饿汉模式在将单例类定义出来后实例就已经存在了饿汉模式没有线程安全问题懒汉模式在使用单例对象的时候才会去创建单例对象的实例节省内存空间懒汉模式存在线程安全问题多个线程同时访问单例的实例
1饿汉模式
多个线程在访问单例对象时没有线程安全问题单例对象已经存在不会出现多个线程创建出多个单例对象的情况。多线程拿到单例对象后在访问单例对象内部的数据时有线程安全问题多线程共享资源
//饿汉模式
class TaskQueue {
public:TaskQueue(const TaskQueue t) delete;TaskQueue operator(const TaskQueue t) delete;static TaskQueue *getInstance() { return m_taskq; }void printTest() { cout i am a public method of a singleton class endl; }private:TaskQueue() default;static TaskQueue *m_taskq;
};TaskQueue* TaskQueue::m_taskq new TaskQueue;//饿汉模式
class TaskQueue {
public:TaskQueue(const TaskQueue t) delete;TaskQueue operator(const TaskQueue t) delete;static TaskQueue *getInstance() { return m_taskq; }void printTest() { cout i am a public method of a singleton class endl; }private:TaskQueue() default;static TaskQueue m_taskq;//已经创建对象
};TaskQueue* TaskQueue::m_taskq;//改为对象声明2懒汉模式
//懒汉模式
class TaskQueue {
public:TaskQueue(const TaskQueue t) delete;TaskQueue operator(const TaskQueue t) delete;static TaskQueue *getInstance() {if (m_taskq nullptr) m_taskq new TaskQueue;return m_taskq;}void printTest() { cout i am a public method of a singleton class endl; }private:TaskQueue() default;static TaskQueue *m_taskq;
};TaskQueue* TaskQueue::m_taskq nullptr;3.懒汉线程安全1
在单例模式中饿汉模式下针对在多线程中可能存在的线程安全问题创建多个实例进行问题修改
1引入互斥锁
在多线程环境下有可能的情况是多个线程同时进入到getInstance()方法中的if语句判断中这时对象就可能被同时创建多个
//懒汉模式 引入互斥锁
class TaskQueue {
public:TaskQueue(const TaskQueue t) delete;TaskQueue operator(const TaskQueue t) delete;static TaskQueue *getInstance() {m_mutex.lock();if (m_taskq nullptr) m_taskq new TaskQueue;m_mutex.unlock();return m_taskq;}void printTest() { cout i am a public method of a singleton class endl; }private:TaskQueue() default;static TaskQueue *m_taskq;static mutex m_mutex;
};mutex TaskQueue::m_mutex;
TaskQueue* TaskQueue::m_taskq nullptr;使用互斥锁对new操作创建实例时进行加锁操作防止同时创建多个实例但是程序执行的效率太低多线程访问单例对象时都是顺序访问
2引入双重检查锁定
双重检查锁定只有第一次访问时是顺序执行的在TaskQueue被实例化出来之后其他线程再去访问单例对象就是并行的了不会进入if内。
//懒汉模式 引入双重检查锁定
class TaskQueue {
public:TaskQueue(const TaskQueue t) delete;TaskQueue operator(const TaskQueue t) delete;static TaskQueue *getInstance() {//双重检查锁定if (m_taskq nullptr) {m_mutex.lock();if (m_taskq nullptr) m_taskq new TaskQueue;m_mutex.unlock(); }return m_taskq;}void printTest() { cout i am a public method of a singleton class endl; }private:TaskQueue() default;static TaskQueue *m_taskq;static mutex m_mutex;
};mutex TaskQueue::m_mutex;
TaskQueue* TaskQueue::m_taskq nullptr;3引入原子变量
通过引入双重检查锁定的方式解决了在懒汉模式下多线程访问单例对象时出现的线程安全问题
从表面上观察引入双重检查锁定的方式是十分完美的但是从底层上依旧存在漏洞 对于 m_taskq new TaskQueue; 操作其对应的机器指令并不是一条而有三条对于计算机来说代码都是二进制指令/机器指令 step1创建一块内存没有数据
step2创建 TaskQueue 类型的对象并将数据写入到对象中
step3为 m_taskq 对象指针初始化将有效的内存地址传递给 m_taskq 对象指针在实际的执行过程中m_taskq new TaskQueue; 对应的机器指令可能会被重新排序成为 step1创建一块内存没有数据
step3为 m_taskq 对象指针初始化将有效的内存地址传递给 m_taskq 对象指针
step2创建 TaskQueue 类型的对象并将数据写入到对象中如果线程A执行完成前两步之后失去CPU时间片被挂起此时线程B在进行指针判断时发现指针 m_taskq 不为空但该指针指向内存没有被初始化导致线程B使用了一个没有被初始化的队列对象就会出现问题出现问题是概率性的 在C11中引入原子变量 atomic在底层控制机器指令的执行顺序可以实现一种更加安全的懒汉模式代码如下 使用原子变量 atomic 的 store() 方法来存储单例对象使用 load() 方法来加载单例对象 在原子变量中这两个函数在处理指令的时候默认的原子顺序是 memory_order_seq_cst 顺序原子操作 使用顺序约束原子操作库整个函数的执行都将保证顺序执行并且不会出现数据竞态 data races 缺点使用这种方法实现的懒汉模式的单例执行效率更低一些 对代码进行以下修改 通过原子变量将类的实例对象保存起来m_taskq 指针指向的内存 类外初始化 指针指向为nullptr 对 getInstance 方法进行相关的修改操作 多线层在调用 getInstance 方法时 需要从原子变量中加载任务队列的实例 抢到互斥锁的线程将继续向下执行 创建实例对象
//懒汉模式 引入原子变量
class TaskQueue {
public:TaskQueue(const TaskQueue t) delete;TaskQueue operator(const TaskQueue t) delete;static TaskQueue *getInstance() {TaskQueue* taskq m_taskq.load();if (taskq nullptr) {m_mutex.lock();taskq m_taskq.load();if (taskq nullptr) {taskq new TaskQueue;m_taskq.store(taskq);}m_mutex.unlock();}return m_taskq.load();}void printTest() { cout i am a public method of a singleton class endl; }private:TaskQueue() default;// static TaskQueue *m_taskq;static atomicTaskQueue* m_taskq;static mutex m_mutex;};mutex TaskQueue::m_mutex;
atomicTaskQueue* TaskQueue::m_taskq;
// TaskQueue* TaskQueue::m_taskq nullptr;4.懒汉线程安全2
在懒汉模式线程安全问题中除了可以通过引入双重检查锁定来解决线程安全问题还可以使用局部静态对象处理线程安全问题
1设置局部静态对象
使用静态的局部对象解决线程安全问题要求编译器必修支持C11标准
在 getInstance() 局部函数中定义一个静态局部对象 static TaskQueue taskq; 调用无参构造初始化在C11标准中规定如果指令逻辑进入一个未被初始化的声明变量所有并发执行应当等待该变量完成初始化 注使用静态的局部对象没有线程安全问题已经由C11标准中的编译器解决未被初始化的变量必须等待其完成初始化才能并发执行 step1创建一块内存没有数据
step2创建 TaskQueue 类型的对象并将数据写入到对象中完成初始化操作
step3为 m_taskq 对象指针初始化将有效的内存地址传递给 m_taskq 对象指针// 懒汉模式 静态局部对象
class TaskQueue {
public:TaskQueue(const TaskQueue t) delete;TaskQueue operator(const TaskQueue t) delete;static TaskQueue* getInstance() {static TaskQueue taskq;return taskq;}void printTest() { cout i am a public method of a singleton class endl; }
private:TaskQueue() default;
};5.简单案例运用
1任务队列简单实现 多线程拿到单例对象后在访问单例对象内部的数据时有线程安全问题多线程共享资源使用互斥锁保护多线程中共享的资源 C11中给互斥锁加/解锁有两种方式 方法1调用mutex对象的 unlock(); lock(); 方法 方法2使用lock_gurd自动管理加/解锁 lock_guardmutex locker(m_mutex); 使用 lock_gurd 可以有效的避免死锁的问题自动加/解锁
// 饿汉模式
class TaskQueue {
public:TaskQueue(const TaskQueue t) delete;TaskQueue operator(const TaskQueue t) delete;static TaskQueue *getInstance() { return m_taskq; }void printTest() { cout i am a public method of a singleton class endl; }// 判断任务队列是否为空bool isEmpty() {lock_guardmutex locker(m_mutex);return m_data.empty();}// 添加任务void addTask(int node) { lock_guardmutex locker(m_mutex);m_data.push(node);}// 删除任务bool removeTask() {lock_guardmutex locker(m_mutex);if (m_data.empty()) return false;m_data.pop();return true;}// 获取队头任务int takeTask() {lock_guardmutex locker(m_mutex);if (m_data.empty()) return -1;return m_data.front();}
private:TaskQueue() default;static TaskQueue *m_taskq;// 任务队列queueint m_data;mutex m_mutex;
};TaskQueue* TaskQueue::m_taskq new TaskQueue;#include iostream
#include thread
#include queue
#include mutex
using namespace std;int main() {// 获取单例对象TaskQueue *taskq TaskQueue::getInstance();taskq-printTest();// 生产者线程// 使用匿名函数指定线程的处理动作thread t1([](){for (int i 0; i 25; i) {taskq-addTask(i 100);cout push data: i 100 , threadId this_thread::get_id() endl;this_thread::sleep_for(chrono::milliseconds(500));//休眠500ms}});// 消费者线程thread t2([](){this_thread::sleep_for(chrono::milliseconds(100));while(!taskq-isEmpty()) {// 开始消费cout --take data: taskq-takeTask() , threadId this_thread::get_id() endl;taskq-removeTask();this_thread::sleep_for(chrono::milliseconds(1000));//休眠500ms}});// 主线程阻塞 只有当t1、t2线程都结束后 主线程解除阻塞t1.join();t2.join();return 0;
}2用户登录
当用户成功登录之后用户名和密码就会被存储到内存中可以创建一个单例类将用户数据保存到单例对象中
class Test {
public:static Test* getInstance() { return m_test; }// m_uservoid setUserName(QString name) {// 多线程下需要加锁解锁涉及写操作// lock();m_user name;// unlock();}QString getUserName(){ return m_user; }// m_passwd// ....// ....// ....
private:Test();Test(const Test t);static Test* m_test;// static Test m_test;// 定义变量 - 属于唯一的单例对象QString m_user;QString m_passwd;QString m_ip;QString m_port;QString m_token;
}
Test* Test::m_test new Test(); // 初始化
// Test Test::m_test;tips部分内容参考课程、书籍与网络等题解、图示及代码内容根据老师课程、二次整理以及自己对知识的理解进行整理和补充仅供学习参考使用不可商业化。