线程安全之单例模式

一、懒汉模式

即第一次调用该类实例的时候才产生一个新的该类实例,并在以后仅返回此实例。需要用锁来保证其线程安全性。原因:多个线程可能进入判断是否已经存在实例的if语句,从而导致线程不安全。使用Double-check Locking来保证线程安全性。但是处理大量数据时,该锁会成为严重的性能瓶颈。


1)静态成员实例的懒汉模式

class Singleton
{
private:
	static Singleton* m_instance;
	Singleton() {}
public:
	static Singleton* getInstance();
};

Singleton* Singleton::getInstance()
{
	if(NULL == m_instance)
	{
		Lock();	// 借用其他类实现,如boost
		if(NULL == m_instance)
		{
			m_instance = new Singleton;
		}
		UnLock();
	}
	return m_instance;
}
这样的代码乍看上去是没有问题的,但函数返回时,m_instance总是指向一个有效的对象。而Lock和UnLock防止了多线程竞争导致的麻烦。双重的if在这里另有妙用,可以让lock的调用开销降低到最小。

但是实际上这样的代码是有问题的。问题的来源便是CPU的乱序执行。C++里的new其实包含了两个步骤:

(1)分配内存。

(2)调用构造函数。

所以m_instance = new Singleton包含了三个步骤:

(1)分配内存。

(2)在内存的位置上调用构造函数。

(3)将内存的地址赋值给m_instance。

在这三步中,(2)和(3)的顺序是可以颠倒的。也就是说,完全有可能出现这样的情况:m_instance的值已经不是NULL,但对象仍然没有构造完毕。这时候如果出现另外一个对getInstance的并发调用,此时第一个if内的表达式m_instance == NULL为false,所以这个调用会直接返回尚未构造完全的对象的地址(m_instance)以提供给用户使用。那么程序这个时候会不会崩溃就取决于这个类的设计如何了。

因此要保证线程安全,阻止CPU换序是必须的。遗憾的是,现在并不存在可移植的阻止换序的方法。通常情况下是调用CPU提供一条指令,这条指令常常被称为barrier。一条barrier指令会阻止CPU将该指令之前的指令交换到barrier之后,反之亦然。换句话说,barrier指令的作用类似于一个拦水坝,阻止换序“穿透”这个大坝。

许多体系结构的CPU都提供barrier指令,不过它们的名称各不相同,例如POWERPC提供的其中一条指令名叫lwsync。我们可以这样来保证线程安全:

#define barrier() __asm__ volatile("lwsync")
Singleton* Singleton::getInstance()
{
	if(NULL == m_instance)
	{
		Lock();	// 借用其他类实现,如boost
		if(NULL == m_instance)
		{
			Singleton* temp = new Singleton;
			barrier();
			m_instance = temp;
		}
		UnLock();
	}
	return m_instance;
}
由于barrier的存在,对象的构造一定在barrier执行之前完成,因此当m_instance被赋值时,对象总是完好的。

2)内部静态实例的懒汉模式

这里需要注意的是,C++0x以后,要求编译器保证内部静态变量的线程安全性,可以不加锁。但C++0x以前仍需要加锁。

class SingletonInside
{
private:
	SingletonInside() {}
public:
	static SingletonInside* getInstance()
	{
		Lock(); // not needed after C++0x
		static SingletonInside instance;
		UnLock(); // not needed after C++0x
		return instance;
	}
};

二、饿汉模式

即无论是否调用该实例,在程序开始时就会产生一个该类的实例,并在以后仅返回此实例。

由静态初始化实例保证其线程安全性。为什么呢?因为静态实例初始化在程序开始时进入主函数之前就由主线程以单线程方式完成了初始化,不必担心初始化问题。

故在性能要求较高时,应使用这种模式,避免频繁的锁争夺。

class SingletonStatic
{
private:
	static const SingletonStatic* m_instance;
	SingletonStatic() {}
public:
	static SingletonStatic* getInstance()
	{
		return m_instance;
	}
};

// 外部初始化 before invoke main
const SingletonStatic* SingletonStatic::m_instance = new SingletonStatic;

三、一个用模板实现的单例模式

struct singleton
{
private:
	struct object_creator
	{
			object_creator() { singleton<T>::instance(); }  // 创建实例
			inline void do_nothing() const { } 
	};
	static object_creator create_object;
	singleton();
public:
	typedef  T  object_type;
	static object_type & instance()
	{
		static object_type obj;
		create_object.do_nothing();// 需要create_object的初始化
		return obj;
	}
};
template <typename T>  typename singleton<T>::object_creator singleton<T>::create_object;


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1. 饿汉式——线程安全单例模式 这是一种最简单的实现方式。在类加载的时候就创建了实例,因此保证了线程安全。缺点是无论是否需要这个对象,都会在程序启动时被加载,从而浪费了一定的空间。 ```java public class Singleton { //创建 Singleton 的一个对象 private static Singleton instance = new Singleton(); //让构造函数为 private,这样该类就不会被实例化 private Singleton(){} //获取唯一可用的对象 public static Singleton getInstance(){ return instance; } } ``` 2. 懒汉式——线程不安全的单例模式 这种方式虽然达到了按需初始化的目的,但却带来了线程不安全的问题,如果多个线程同时调用 `getInstance()` 方法,那么就会创建多个实例。 ```java public class Singleton { private static Singleton instance; private Singleton(){} public static Singleton getInstance(){ if(instance == null) { instance = new Singleton(); } return instance; } } ``` 3. 懒汉式——线程安全单例模式 使用 `synchronized` 关键字可以解决线程安全问题,但是这样每次调用 `getInstance()` 方法都会进行同步,影响程序的性能。 ```java public class Singleton { private static Singleton instance; private Singleton(){} public static synchronized Singleton getInstance() { if(instance == null) { instance = new Singleton(); } return instance; } } ``` 4. 双重校验锁——线程安全单例模式 这是一种比较好的实现方式,使用了双重校验锁,既保证了线程安全,又实现了按需初始化,同时也减少了同步开销。 ```java public class Singleton { private volatile static Singleton instance; private Singleton(){} public static Singleton getInstance() { if(instance == null) { synchronized (Singleton.class) { if(instance == null) { instance = new Singleton(); } } } return instance; } } ``` 5. 静态内部类——线程安全单例模式 使用静态内部类的方式可以在调用 `getInstance()` 方法时才真正创建对象,达到最佳的按需初始化效果,并且也保证了线程安全。 ```java public class Singleton { private Singleton(){} private static class SingletonHolder{ private static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance(){ return SingletonHolder.INSTANCE; } } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值