线程安全的单例模式C++实现

单例模式的介绍

单例模式的优点是只有一个对象,可以节省资源,提高运行速度,提供一个全局的访问点。
在使用过程中,写一个具有单例模式思想的程序可以很简单,但是如果要做到线程安全并且生成实例的过程中符合业务需求,是一件困难的事情。

Lazy mode(懒汉模式)

懒汉模式,会将实例化的时间延迟到正式调用接口的时候。
简单的实现方式

class singleton
{
public:
	singleton* getInstance()
	{
		if(p_singletonIns == nullptr)
	{
	p_singletonIns = new singleton();
}
	return p_singletonIns;
}

private:
	singleton()=default;
	singleton(const singleton &cpSingleton) = delete;
	singleton& operator=(const singleton& cpSingleton) = delete;
	static singleton* p_singletonIns;
};

singleton* singleton::p_singletonIns = nullptr;

上面的用例中,可以看到上面的p_singletonIns的在第一次调用getInstance()的时候会将p_singletonIns赋予一个实例的地址。这种延迟到使用时才进行实例化的做法,叫做懒汉模式。
但是这种方法不是线程安全的,同时会存在内存泄露的问题。
使用指针的方式的话,在创建实例的时候一般使用DCL(Double Check Lock), 双检测锁。代码如下所示

class singleton
{
public:
	singleton* getInstance()
	{
		if(p_singletonIns == nullptr) // p1
		{
			std::lock_guard<std::mutex> lock();
			if(p_singletonIns == nullptr) // p2
			{
				p_singletonIns = new singleton(); // p3
			}
		}
		return p_singletonIns;
	}

private:
	singleton()=default;
	singleton(const singleton &cpSingleton) = delete;
	singleton& operator=(const singleton& cpSingleton) = delete;
	static singleton* p_singletonIns;
};

singleton* singleton::p_singletonIns = nullptr;

但是在使用过程中, p1的判断是存在一个风险,线程仍然不安全。就是在p3在new的时候,在new这个过程中,p_singletonIns的指针会被先赋值,然后开始创建内存。在并发的情况下,p1可能判断出p_singletonIns不是空指针,但是内存其实没有完全分配完成,那么造成返回p_singletonIns这个指针其实是不可用的。

使用原子变量的懒汉模式

在C++11的标准库中有原子变量的实现。使用原子变量就可以上述这种避免线程不安全的问题。

std::mutex mutexIns;
class singleton
{
public:
static singleton* getInstance()
{
	if(p_singletonIns.load() == nullptr) // p1
	{
		std::lock_guard<std::mutex> lock(mutexIns);
		if(p_singletonIns.load() == nullptr) // p2
		{
			p_singletonIns.store(new singleton()); // p3
		}
	}
	return p_singletonIns.load();
}

private:
	singleton()=default;
	singleton(const singleton &cpSingleton) = delete;
	singleton& operator=(const singleton& cpSingleton) = delete;
	static std::atomic<singleton*> p_singletonIns;
};
std::atomic<singleton*> singleton::p_singletonIns(nullptr);

使用局部静态变量的懒汉模式

使用局部静态变量也可以实现懒汉模式,使用局部静态变量在C++11之后是线程安全。这也是最为优雅的一种方式。

class singleton
{
public:
	static singleton* getInstance()
	{
		singleton singletonIns;
		return &singletonIns;
	}

private:
	singleton()=default;
	singleton(const singleton &cpSingleton) = delete;
	const singleton& operator=(const singleton& cpSingleton) = delete;
};

饿汉模式

饿汉模式:故名思意,一个饿坏的人会将一切能吃的东西都塞到肚子里,吃不下去的也要拽在手里。
在编程中这种模式的体现就在于资源在程序启动的时候就会分配好。而不是在运行时才进行资源分配。
使用饿汉模式编写单例模式相对简单一些。

class singleton
{
public:
	singleton* getInstance()
	{
		return &singletonIns;
	}

private:
	singleton()=default;
	singleton(const singleton &cpSingleton) = delete;
	singleton& operator=(const singleton& cpSingleton) = delete;
	static singleton singletonIns;
};
singleton singleton::singletonIns;

使用饿汉模式的话,singletonIns在C++中进行初始化的过程发生在程序加载的过程中,在main运行之前,也在调用getInstance()之前。这个初始化的过程也可以知道是确保线程安全的。同时也可以保证在程序结束之后自动析构。

关于资源泄露

首先资源泄露并不只是意味着内存泄露,还会指网络资源的释放,比如在分布式系统中,在程序退出时,让其他系统知道程序的退出等等场景。
在单例模式中,因为使用全局变量或者静态变量进行构造的时候,在程序退出时,会自动进行析构,通常是不存在资源泄漏的。在使用new的情况下,则不会自动进行析构,会存在资源泄漏的问题。下面介绍使用堆内存来构造单例模式的时候,如何在程序推出时,自动析构。

使用静态的嵌套对象来进行释放

使用嵌套的对象变量来释放,是因为单例模式的析构函数是private权限的,因此要使用嵌套对象来解决这个private的访问权限的问题。也满足高内聚编程原则。

std::mutex mutexIns;
class singleton
{
public:
class deletor 
{
public:
    ~deletor()
    {
        singleton *singletonIns;
        if((singletonIns = getInstance()) != nullptr)
        {
            delete singletonIns;
        }
    }
};

static singleton* getInstance()
{
	if(p_singletonIns.load() == nullptr) // p1
	{
		std::lock_guard<std::mutex> lock(mutexIns);
		if(p_singletonIns.load() == nullptr) // p2
		{
			p_singletonIns.store(new singleton()); // p3
		}
	}
	return p_singletonIns.load();
}
	
private:
	singleton()=default;
	singleton(const singleton &cpSingleton) = delete;
	singleton& operator=(const singleton& cpSingleton) = delete;
	~singleton()
    {
        //do anything to make sure you have prevented resource leak.
        //...
    }
	static std::atomic<singleton*> p_singletonIns;
    static deletor   deletorIns;
};
std::atomic<singleton*> singleton::p_singletonIns(nullptr);
singleton::deletor   deletorIns;

你可能感兴趣的:(单例模式,安全,c++)