目录
为什么需要设计模式
简单来说,设计模式就是在解决某一类问题时,有既定的优秀的代码框架可以用,优点如下:
- 代码易于维护;
- 能够做到软件设计的开-闭原则(对修改关闭,对扩展开放);
- 合理的设计模式,能够做到软件设计的高内聚,低耦合。
什么是单例模式?
单例模式指的是,你无论怎么获取,永远只能得到该类的一个实例对象,所以单例模式的设计有如下三步曲:
- 构造函数私有化,去除拷贝构造和赋值重载;
- 提供一个私有的静态实例对象(饿汉式)或者静态对象指针(懒汉式);
- 提供一个对外的静态的成员方法,获取该对象。
首先区别两个概念:饿汉式单例模式与懒汉式单例模式(根据对象的创建时机不同来区分):
- 如果在使用对象之前,对象就已经创建好了,则称为饿汉式;
- 如果在使用对象之时,我们才开始创建对象,则成为懒汉式。
什么是线程安全?
既然是线程安全问题,那么毫无疑问,所有的隐患都是在多个线程访问的情况下产生的,也就是我们要确保在多条线程访问的时候,我们的程序还能按照我们预期的行为去执行;具体在单例模式中(懒汉式)的表现就是:在多线程并发环境下,我们要确保只有一个线程会创建类的实例对象,其它线程基于这个对象进行操作,而不会再去创建实例。懒汉式如果不加以措施的话默认是线程不安全的,饿汉式由于对象在使用之前就已经创建好了,所以是线程安全的。
饿汉式:
//饿汉式
//不用考虑线程安全问题,在程序启动的时候就已经调用构造函数开辟空间实例化对象了
class Singleton
{
public:
/*3.获取类的唯一实例对象的接口方法*/
static Singleton* getInstance() //静态的成员方法,访问私有的静态成员数据
{
return &instance;
}
private:
/*2.定义一个唯一的类的实例对象*/
static Singleton instance; //私有的静态成员数据,只能类内访问,类外无法引用
/*1.构造函数私有化*/
Singleton()
{
}
//去掉拷贝构造和赋值重载
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
};
Singleton Singleton::instance; // 静态变量,类内声明,类外定义
针对以上代码,回答一些问题:
(1)为什么构造函数私有化,去除拷贝构造和赋值重载?
这样保证了我们外部是无法随意创建对象的。
(2)为什么是静态实例和静态方法?
首先我们要知道,类外调用类中的成员方法有两种方式:
- 创建一个该类的对象,通过对象调用;
- 通过类名加作用域::来调用(类中方法必须是静态的)。
由于构造函数私有化,我们无法获得类的一个实例,所以通过第一种方式显然不行,那么只能通过第二种方式,将成员方法定义为静态的,而由于静态的成员方法只能访问静态的成员数据,所以提供的对象实例也要设置为静态的。
注意:对类的静态成员数据,要类内声明,在类外进行定义。
线程安全的懒汉式单例模式方法1:
std::mutex mtx;
//懒汉式 线程安全方式1
class Singleton
{
public:
/*3.获取类的唯一实例对象的接口方法*/
// 是不是可重入函数呢?不是 锁+双重判断---->线程安全的懒汉式单例模式
static Singleton* getInstance() //静态的成员方法,访问私有的静态成员数据
{
//lock_guard<std::mutex> guard(mtx); //锁的粒度太大了
if(instance == nullptr)
{
lock_guard<std::mutex> guard(mtx); //锁放这,减小粒度
if(instance == nullptr)
{
/* 非原子操作
开辟内存
构造函数
给instance赋值
*/
instance = new Singleton();
}
}
return instance;
}
private:
/*2.定义一个唯一的类的实例对象指针*/
static Singleton *volatile instance; //私有的静态成员数据,只能类内访问,类外无法引用
/*1.构造函数私有化*/
Singleton()
{
}
//去掉拷贝构造和赋值重载
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
};
Singleton *volatile Singleton::instance = nullptr; // 静态变量,类内声明,类外定义
步骤:构造函数私有化,去除拷贝构造和赋值重载;提供一个静态的指向实例对象的指针;提供一个静态的成员方法,在该方法里面实例化对象。
注意:上面用到了volatile关键字,保证了对volatile修饰的变量编译器不会进行优化,否则我们在执行new操作时可能由于编译的优化,代码逻辑不会按照我们预期的执行。其次volatile还有一个作用:可见性,对volatile修饰的变量的操作是直接在主存上操作的,是不经过高速缓存与寄存器的,所以一个线程对其进行操作之后,其余线程保证能立刻感受到其变化。
线程安全的懒汉式单例模式方法2:
//懒汉式 线程安全方式2
class Singleton
{
public:
//线程安全精简的懒汉单例模式
static Singleton* getInstance() //静态的成员方法,访问私有的静态成员数据
{
static Singleton instance; //函数内部的一个静态局部变量,程序启动阶段内存就分配好了,在数据段
//静态对象的初始化是在程序第一次运行到它的时候才初始化
//函数静态局部变量的初始化,在汇编指令上已经自动添加互斥指令了
//不用担心线程安全的问题
return &instance;
}
private:
/*1.构造函数私有化*/
Singleton()
{
//很多初始化的代码
}
//去掉拷贝构造和赋值重载
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
};