Linux线程(七)

Linux线程(七)

一、单例模式

  • 1.什么是单例模式?

单例模式是一种经典的“设计模式”,保证类在内存中只能有一个对象

  • 2.什么是设计模式?
  • 设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。
  • 使用设计模式是为了重用代码、让代码更容易被他人理解、保证代码可靠性。
  • 毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。
  • 项目中合理地运用设计模式可以完美地解决很多问题,每种模式在现实中都有相应的原理来与之对应,每种模式都描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,这也是设计模式能被广泛应用的原因。
  • 3.单例模式的特点:
  • 私有化的构造函数
  • 私有的静态的全局变量
  • 公有的静态的方法
  • 4.饿汉式和懒汉式的实现方法:
  • 饿汉式:类加载的时候就实例化,并且创建单例对象,简单来说,就是吃完饭立即洗完,下次就直接使用
template<class T>
class Singleton
{
public:
	static T*  GetInstance()
	{
		return &data;
	}

private:
	static T date;
};
  • 懒汉式:默认不会实例化,什么时候用什么时候new
  • 简单来说,懒汉式就是吃完饭不立即洗碗,而是什么时候用,什么时候洗。懒汉式的优点思想是“延时加载”,从而能够优化服务器的启动数度
template<class T>
class Singleton
{
public:
static T* GetInstance()
{
	if(inst == NULL)
	{
		inst == new T ();
	}
	return inst;
}

private:
	static T* inst;
	
};

这样写懒汉式的实现方法有一个严重的缺陷:线程不安全,第一次调用时如果有两个线程同时调用,可能会创建出两个T类型对象的实例,后面调用就不会出现问题了。

  • 懒汉式实现单例模式的线程安全版本
template<class T>
class Singleton
{
public:
	//双重判定空指针,降低锁冲突的概率,提高性能,使用互斥锁,保证多线程
	//下一次也只能new出一个对象实例
	static T* GetInstence()
	{
		if(inst == NULL)//第一重检查,检查inst是否已经指向某个对象实例,
						//注意此处在多线程的高并发情况下,可能会有多个
						//线程进入到第一个if里面
		{
			lock.lock();//加锁,保证在多线程的情况下一次也只能有一个线程进入
			if(inst == NULL)//第二重检查,检查inst是否为空
			//假设没有第二重检查,那么前面说过,进入第一个if
			//里面的线程可能有多个,此时第一个线程获得到锁后,
			//new出一个对象,释放锁,接着第二个线程或得到锁,
			//继续new出对象,这样就不满足单例模式的要求。
			{
				inst = new T();
			}
			lock.unlock();
		}
		return inst;
	}


private:
	//使用volatile关键字,防止被编译器优化
	volatile static T* inst;//这里使用volatile关键字的原因是:
	//因为inst = new T();这条语句是非原子的,实际上会执行以下操作:
	//a.在堆上开辟空间 b.初始化 c.把inst指向开辟的空间。
	//假设不加volatile关键字,那么正常的执行顺序是abc,那么被编译器
	//优化后的执行顺须就可能变为bca等,就达不到new的目的,所以voaltile
	//的目的是禁止编译器优化
	static std::mutex lock;
};

二、STL、智能指针和线程安全

  • 1.STL中的容器是否线程安全:

答案是:

  • 不是线程安全的。原因是:STL的设计初衷是将性能提升到极致,简单来说STL就是为了代码运行效率设计的,而一旦设计线程安全问题,会对代码的性能造成巨大影响
  • STL是在C++98就提出了,而线程安全是在C++11提出的
  • 2.智能指针是否线程安全?
  • 对于unique_ptr:由于只是在当前代码块的范围内生效,因此不涉及线程安全问题
  • 对于shared_ptr:多个对象需要需要共享一个引用计数变量,所以存在线程安全问题,但是标准库实现的时候考虑到这个问题,基于原子操作的(CAS)方式保证shared_ptr的能够高效,原子的操作引用计数

三、其他常见的锁

  • 悲观锁:在每次取数据时,总是担心数据会被其他线程修改,所以会在取数据前先加锁(读锁,写锁,行锁等),当其他线程想要访问数据时,被阻塞挂起
  • 乐观锁:每次取数据时候,总是乐观的认为数据不会被其他线程修改,因此不上锁。但是在更新数据前,会判断其他数据在更新前有没有对数据进行修改。主要采用两种方式:版本号机制和CAS操作

CAS操作:当需要更新数据时,判断当前内存值和之前取得的值是否相等。如果相等则用新值更新。若不等则失败,失败则重试,一般是一个自旋的过程,即不断重试

  • 自旋锁,公平锁,非公平锁
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页