单例模式:懒汉,饿汉模式

 懒汉模式

所谓懒,就是只有在第一次调用的时候才会去分配内存,通过判断指针是否为空去决定需不需要new Lazy();实现很简单如下。

#include <iostream>
 
using namespace std;

class Lazy {
public:
    static Lazy* getInstance() {
		if (mLazy == nullptr) {
			mLazy = new Lazy();
            cout << "create lazy" << endl;
		}
        cout << "lazy" <<"["<< mLazy <<"]"<< endl;
		return mLazy;
	}
private:
    static Lazy* mLazy;
};
Lazy* Lazy::mLazy = nullptr;

int main() {
    cout << "Main" << endl;
	Lazy::getInstance();
	Lazy::getInstance();
}

问题:但是懒汉模式在多线程下是不安全的,所谓不安全就是:多个线程同时去调用getInstance()函数,线程会同时满足为空的条件,可以看下面的例子,不一定会100%出现,多做几次或者多写几个线程去多执行几次程序,会比较容易复现。

#include <iostream>
#include <thread>

#include <sys/types.h>
#include <unistd.h>
using namespace std;

class Lazy {
public:
    static Lazy* getInstance() {
		if (mLazy == nullptr) {
			mLazy = new Lazy();
            cout << "create lazy" << endl;
		}
        cout << "lazy" <<"["<< mLazy <<"]"<< endl;
		return mLazy;
	}
private:
    static Lazy* mLazy;
};
Lazy* Lazy::mLazy = nullptr;

void fun(int i){
    cout << "fun  1  " << getpid() << endl;

	Lazy::getInstance();
    cout << "fun  2  " << getpid() << endl;
	while(1) {
		sleep(5);
	}
}
void fun2(int i){
    cout << "fun  3  " << getpid() << endl;

	Lazy::getInstance();
    cout << "fun  4  " << getpid() << endl;
	while(1) {
		sleep(5);
	}
}

int main() {
    cout << "Main" << endl;
	thread t1(fun, 1);
	t1.detach();
	thread t2(fun2, 1);
	t2.detach();
	while(1) {
		sleep(5);
	}

};

 分析:下图为结果可以看到,两个线程同时create lazy,并且mLazy被赋了不同的值。所以之后如果有地方去调用getInstance()肯定是有问题的。

【注:图片显示是两个lazy的地址不一致】

  •  解决:常见的办法有两种:

1.在判空之前去加锁,这样保证在new Lazy()只被调用一次。

单例实现起来是这样的:

    static Lazy* getInstance() {
		i_mutex.lock();   //上锁
		if (mLazy == nullptr) {

			mLazy = new Lazy();
            cout << "create lazy" << endl;
		}
		i_mutex.unlock(); //解锁
        cout << "lazy" <<"["<< mLazy <<"]"<< endl;
		return mLazy;
	}

但是这样会带来问题:

  • 频繁的去上锁,解锁相当的影响性能,所以出现了二重锁。实现起来是这样的:
    static Lazy* getInstance() {
		if (mLazy == NULL) {  // 为空条件NULL
            i_mutex.lock(); //上锁
            if (mLazy == nullptr) {  
                mLazy = new Lazy();
                cout << "create lazy" << endl;
            }
            i_mutex.unlock(); //解锁
		}
        cout << "lazy" <<"["<< mLazy <<"]"<< endl;
		return mLazy;
	}

  • 这样是多线程安全的。

1.当A ,B 线程都满足第一个mLazy == NULL的条件,总有一个线程先拿到锁,比如A

2.A上锁,B阻塞

3.A判空,new Lazy()

4.A 解锁。 return mLazy;

5.B不阻塞,拿到锁。判空

6.B发现非空,解锁

7.B return mLazy;

  • 不会频繁的上下锁带来性能损耗,因为有非空的判定!

2.利用局部静态变量只初始化一次的特性去new Lazy();这样根本不需要mLazy这个变量了。为什么是线程安全的呢?因为局部static静态变量只会初始化一次,线程A,B不论谁先去调用getInstance(),instance只会被初始化一次(即只调用new Lazy()一次)。这样可以去保证线程安全。

    static Lazy* getInstance() {
        static Lazy* instance = new Lazy();
        cout << "lazy" <<"["<< mLazy <<"]"<< endl;
		return instance;
	}

饿汉模式

关于饿汉模式,所谓饿汉就是程序起始就会去创建分配内存,所以实现如下。程序的调用过程:

1.静态变量初始化:new Hungry();来初始化mHungry变量。

2.进入main()函数,这里就是饿汉模式的精髓,在所有的业务代码执行之前(即执行main函数的代码之前),mHungry已经有了调用了new Hungry()赋值了,所以可以保证mHungry的调用不会像多线程那样出现问题。所以饿汉模式是线程安全的。

#include <iostream>
 
using namespace std;

class Hungry {
public:
    Hungry() {
		cout << "Hungry" << endl;
		cout << "mHungry: " << mHungry << endl;
	}
    static Hungry* getInstance() {
		cout << "mHungry: " << mHungry << endl;

		return mHungry;
	}
private:
    static Hungry* mHungry;
};
Hungry* Hungry::mHungry = new Hungry();

int main() {
    cout << "Main" << endl;

	Hungry::getInstance();
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值