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

 懒汉模式

所谓懒,就是只有在第一次调用的时候才会去分配内存,通过判断指针是否为空去决定需不需要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()肯定是有问题的。

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

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;
	}

为什么需要为空判定两次呢?

如果没有这个条件(mLazy == nullptr),A,B两个线程同时满足(mLazy == NULL)的条件后,A先new Lazy(),此时mLazy已经不为空了,但是B线程已经进入(mLazy == NULL)的条件,所以会继续new Lazy(),所以仍然是线程不安全的。所以需要加上mLazy == nullptr的判定,来保证线程安全。

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();
}

猜你喜欢

转载自blog.csdn.net/m0_37844072/article/details/120857186