单例模式
注意注意注意:我们这里讲的线程安全只是说的是创建单例对象时的线程安全,就是保证不会创建多个单例对象。不管是饿汉式还是懒汉式,多线程读写的安全性我们在这篇文章都没考虑。
单例模式一句话的来讲就是,这个类只存在一个实例化对象。
如何实现实例化?
既然只能有一个实例化对象,那么就要防止被多次实例化,如何防止?就要把构造函数私有化或者受保护,这样构造函数就无法被外部调用,只能被类内部的成员函数调用,所以我们需要定义一个公有函数来供外部使用,这个公有函数返回一个对象,为了保证多次调用这个公有函数都会返同一个对象,就需要将这个给对象设置为静态的。
简单总结一下需要注意三点:
一、构造函数需要私有或者受保护;
二、定义静态对象;
三、定义静态公有函数
单例模式分为懒汉模式和饿汉模式。
懒汉模式,一开始不实例化,等到调用的时候才进行实例化,顾名思义,比较懒,用时间换空间。
饿汉模式,一开始就进行了实例化,顾名思义,比较饿,用空间换时间。
懒汉模式
可以通过加锁来达到线程安全
//懒汉式单例模式,第一次用到类实例的时候才去实例化
//单线程下是正确的,多线程同时检测到Instance=NULL,则两个线程同时构造一个实例给Instance
class singleton1{
private:
singleton1(){} //私有的构造函数
static singleton1* Instance;//全局访问点
public:
static singleton1* GetInstance(){
if(Instance == NULL){
//使用无参构造函数不加括号
Instance = new singleton1;
}
return Instance;
}
};
singleton1* singleton1::Instance = NULL;
饿汉模式
class singleton2{
private:
singleton2(){};
//指向singleton2对象的指针
static singleton2* Instance;
public:
static singleton2* GetInstance(){
return Instance;
}
};
singleton2* singleton2::Instance = new singleton2;// 在程序入口之前就完成单例对象的初始化
饿汉模式为什么比懒汉模式使用起来更安全?
饿汉比懒汉更安全主要体现在线程安全上面,原因如下:
假如是懒汉模式,线程一在调用公有函数时,判断p是空,进行初始化,线程二也在调用该公有函数,也在判断p是空,也给他初始化。两个线程同时再给他初始化岂不是乱套!但是如果是饿汉模式,由于一开始就已经给p初始化了,多个线程在调用公有函数时,直接拿即可,不存在任何问题,也就避免了懒汉的那种尴尬情况。
虽然说在懒汉的公有函数里给判断前后加个锁可以有效解决这个问题,但是即使这样,和饿汉比起来会很繁琐,故孰优孰劣,大家懂的。
面试可能会问的问题
单例可以问一堆问题
1。 如何保证唯一?
2。如何处理初始化依赖?
3。初始化时,是否线程安全
4。进程退出时,能否自动释放资源?
如何保证唯一:防止被多次实例化,如何防止?就要把构造函数私有化或者受保护,这样构造函数就无法被外部调用,只能被类内部的成员函数调用,所以我们需要定义一个公有函数来供外部使用,这个公有函数返回一个对象,为了保证多次调用这个公有函数都会返同一个对象,就需要将这个给对象设置为静态的。
如何处理初始化依赖:
初始化时,是否线程安全:懒汉如果不加锁就不安全,加锁就可以安全,但是加了锁之后和饿汉比起来会很繁琐。饿汉是安全的。
进程退出时,能否自动释放资源:需要实现一个内嵌回收类。