简介:
单例模式是一种常用的软件设计模式,其定义是单例对象的类 只允许一个实例存在。
许多时间我们系统只需要拥有一个全局对象,这样有利于我们协调系统的整体的行为。
单例模式(Singletion)
保证一个类仅有一个实例,并提供一个访问该实例的全局访问点。
基本实现思路:
单例模式要求类能够有返回对象的一个接口 和 一个获得该实例的接口(静态方法)
①,该类的构造方法定义为私有方法,这样就无法通过调用该类的构造方法来实例化该类的对象,只有通过该类提供的静态方法得到该类唯一的实例对象。
②,该类内提供一个静态方法,当我们调用这个方法时,if类持有的接口不为空就返回这个接口所指向的内容,else创建该类的实例并用该接口指向该单例
对象注意事项:
单例模式在多线程的应用场景下必须小心使用。会出现的问题以及如何解决下面会讲述。
单例模式的主要作用
保证该对象的唯一性。同时严格控制客户端怎样访问该实例以及如何访问它。(唯一实例的受控访问)
懒汉式单例模式:第一次被引用的时间才会将类实例化。
情况1:下面代码是没有考虑多线程安全的普通代码的书写。
class Solution{
public:
static Database* Get_Database()
{
if(mybase == NULL)
{
mybase = new Database;
}
return mybase;
}
private:
static Database* mybase;
}
Database* Solution:: mybase = NULL;//静态的类成员变量在类外初始化
分析:当pthread1进入到Get_database()函数的if判断内该线程的时间片刚好用完,此时pthread2调用Get_database()初始化了一个mybase。但是pthread1又获得了时间片,但是他不用判断mybase 是否 为NULL。此时就不是单例了(这样有可能会导致之前的数据丢失)。
优化:针对情况1,我们给初始化mybase时,加上锁的操作。
mutex mymutex;//定义一个全局的锁。
//将上述的Get_Database()修改为下面的
//加锁方法①
static Database* Get_Database()
{
if(mybase == NULL)
{
mymutex.lock();
mybase = new Database;
mymutex.unlock();
}
return mybase;
}
//加锁方法②
static Database* Get_Database()
{
mymutex.lock();
if(mybase == NULL)
{
mybase = new Database;
}
mymutex.unlock();
return mybase;
}
分析:
第一种加锁方式中,其实和情况1出现的情况就是一样的。pthread1在if判断和加锁之间时间片到了,phtread2初始化了该数据库,此时pthread1便会继续new,导致上面的那个情况。
第二种加锁方式。该方式可以避免了上述线程不安全的问题。但是同时他又出现了另外一个问题:效率问题,我们设计一个项目不仅仅要求他们能够正常运行,还要考虑效率问题。这种方式加锁,导致多线程每次获取该数据库都会在获取锁时阻塞。这样就导致了时间效率的告诉下滑。
那么如何解决呢??上面的问题是出现在可能多个线程出现在第一个if判断和加锁之间,那么我们不妨在加一层判断,便可。
优化:
mutex mymutex;
static Get_Database()
{
if(mybase == NULL)
{
mymutex.lock();
if(mybase == NULL)
{
mybase = new Database;
}
mymutex.unlock();
}
return mybase;
}
分析:这样就成功的避免了上述所说的线程不安全的问题。
饿汉式单例模式:只要类被加载的时间,就会将类实例化。
class Solution{
public:
static Database* Get_Database()
{
//直接返回一个实例化完成的对象
return mybase;
}
private:
static Database* mybase;
Solution(){ }//私有构造函数
}
Database* Solution:: mybase = new Database;//无论是否引用这类类对象,我们直接给他实例化。
静态实例初始化在程序开始时进入主函数之前,就由主线程以单线程方式完成了初始化--饿汉式单例类,也就是静态初始化实例保证其线程安全性,故而在性能要求较高时,应该使用这种模式,避免繁琐的锁争夺。
饿汉式优点: | 写法比较简单,在类的装载的时间完成了实例化。避免了线程同步的问题。 |
饿汉式缺点: | 在类的装载的时候就完成了实例化,没有达到Lazy Loading的效果。如果从始至终都从未使用过这个实例,则会造成内存的浪费。 |
拓展部分:
我们首先来看一下,new操作的步骤:①申请空间②构造该空间对象③将该空间的地址给mybase。