设计模式之单例模式 (模板 智能指针 删除器) 实现【懒汉】

上一篇关于单例模式的实现 主要是Doubke-Check Locking。

但是多线程环境下并不能保证完全线程安全。


这篇文章实现的单例:

懒汉模式 的单例

基于模板和继承

线程安全

使用智能指针 防止内存泄露

效率相对较高


实现代码如下:

#include <iostream>
#include <memory>
#include <windows.h>
#include <process.h>

using namespace std;

template <class T>
class CSingletonPtr
{
// 私有删除器
private:
	class Deleter
	{
	public:
		// 重载()操作符
		void operator()(T* p)
		{
			delete p;
		}
	};
private:
	static tr1::shared_ptr<T> m_pInstance;	// 智能指针 避免内存泄露

private:
	CSingletonPtr(const CSingletonPtr&){}
	CSingletonPtr& operator=(const CSingletonPtr&){}
protected:
	// 需要继承 所以一定要声明为public
	CSingletonPtr()
	{
		cout << "CSingletonPtr begin construct" << endl;
		::Sleep(1000);
		cout << "CSingletonPtr end construct" << endl;
	}
	virtual ~CSingletonPtr()
	{
		cout << "CSingletonPtr destrcut" << endl;
	}
public:
	// 返回引用 避免拷贝构造和析构 提高效率
	static T& getInstance()
	{
		// static 表明变量是跨线程共享的
		// volatile 表示不进行编译器优化
		static volatile long lock = 0;
		/*
		if (lock == 0)
		{
			lock = 1;
		}

		分析上面这段代码, 考虑下面这种情况:
		线程1正在执行 if(lock == 0)的判断
		此时切换到线程2 线程2正在执行lock = 1;

		因此会出问题 这种情况需要避免。

		使用InterlockedCompareExchange函数解决上面出现的问题
		一个线程调用该函数 那么会立即锁定变量内存地址 其他线程不可同时访问
		注:只能锁定32位(4字节)变量
		*/

		int res = 0;
		// 只有第一次调用InterlockedCompareExchange的线程才会跳过下面的if块 创建单例的实例 并返回
		// 使用编译器提供的本质函数 _InterlockedCompareExchange 提高效率
		if (res = ::_InterlockedCompareExchange(&lock, 1, 0) != 0)
		{
			cout << res << "---" << " Thread ID = " << ::GetCurrentThreadId() << endl;
			// 不是第一次调用该函数的线程 会一直轮询等待
			// 直至第一次调用该函数的线程创建好单例并返回单例实例 才会结束轮询
			while (lock != 2)
			{
				::Sleep(0);
			}
			return *m_pInstance.get();
		}
		tr1::shared_ptr<T> tmp(new T(), T::Deleter());
		m_pInstance = tmp;
		lock = 2;			// 内存分配好了就让其他线程停止等待
		cout << "内存分配初始化完成" << endl;
		return *m_pInstance.get();
	}
};

// 模板静态成员初始化***
template <class T>
tr1::shared_ptr<T> CSingletonPtr<T>::m_pInstance = NULL;

// 实例化模板
class Manager : public CSingletonPtr<Manager>
{
	// 访问权限授权给基类
	friend class CSingletonPtr<Manager>;
protected:
	Manager()
	{
		cout << "Manager begin construct" << endl;
		::Sleep(500);
		m_count = 0;
		cout << "Manager end construct" << endl;
	}
	~Manager()
	{
		cout << "Manager destrcut" << endl;
	}
public:
	void print()
	{
		cout << "Hello, this is Manager " << m_count++ << endl;
	}
private:
	int m_count;
};

//-----------------多线程测试-----------------
unsigned int __stdcall threadFun(void*)
{
	cout << "Current Thread ID = " << ::GetCurrentThreadId() << endl;

	Manager::getInstance().print();
	return 0;
}

void testMultiThread()
{
	for (int i = 0; i < 3; ++i)
	{
		uintptr_t th = ::_beginthreadex(NULL, 0, threadFun, NULL, 0, NULL);
		::CloseHandle((HANDLE)th);
	}
}

int main(void)
{
	testMultiThread();
	getchar();

	return 0;
}

贴一下运行结果图:


注意到代码中针对原子锁的使用的代码。

第一次调用res = 0 不会进入if语句块 不会打印 "res --- Thread ID = "这句话

线程切换后 会打印那句话 我们可以看到 剩余的两个线程都打印了。


这正好验证了我们的思路。

第一个线程创建单例返回后 其余线程才会退出等待。



另外  内存分配完成后,调用getInstance()获取单例。

然后调用print()打印 "Hello, this is Manager"那句话

多次运行结果可能不太一样。


因为 内存分配完成后 

CPU的所有权现在不确定在哪个线程上,获取到线程都可以去执行print()函数。

这时候不存在线程等待和轮询的问题。


可以在print()方法打印语句中 输出当前线程ID 进行调试。


欢迎大家批评指正~


阅读更多
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u013575812/article/details/51005045
个人分类: 设计模式
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭