设计模式——单例模式(C++实现)

一、单例模式定义:

保证一个类仅有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。

二、应用场景:

比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息。这种方式简化了在复杂环境下的配置管理。其他还有如系统的日志输出、MODEM的联接需要一条且只需要一条电话线,操作系统只能有一个窗口管理器,一台PC连一个键盘等等。

三、方式:

根据单例对象创建时间,可分为两种模式:饿汉模式 + 懒汉模式。

  • 懒汉模式:指全局的单例实例在第一次被使用时构建。

  • 饿汉模式:指全局的单例实例在类装载时构建。

四、代码实现

1.懒汉模式

class Singleton {
private:
	Singleton() {}  //构造函数是私有的
	static Singleton *instance;
public:
	static Singleton * GetInstance() {
		if(instance == nullptr)  //判断是否第一次调用
			instance = new Singleton();
		return instance;
	}
};
Singleton * Singleton::instance = nullptr;

单例类Singleton有以下特征:
它有一个指向唯一实例的静态指针instance,并且是私有的;
它有一个公有的函数,可以获取这个唯一的实例,并且在需要的时候创建该实例;
它的构造函数是私有的,这样就不能从别处创建该类的实例。

2.懒汉模式+对象释放

程序在结束的时候,系统会自动析构所有的全局变量。事实上,系统也会析构所有的类的静态成员变量,就像这些静态成员也是全局变量一样。利用这个特征,我们可以在单例类中定义一个这样的静态成员变量,而它的唯一工作就是在析构函数中删除单例类的实例。如下面的代码中的Garbo类(Garbo意为垃圾工人):

class Singleton {
private:
	Singleton() {}
	static Singleton *m_pInstance;
	class Garbo { //它的唯一工作就是在析构函数中删除Singleton的实例
	public:
		~Garbo() {
			if(Singleton::m_pInstance)
				delete Singleton::m_pInstance;
		}
	};
	static Garbo garbo;  //定义一个静态成员变量,程序结束时,系统会自动调用它的析构函数
public:
	static Singleton * GetInstance() {
		if(m_pInstance == nullptr)  //判断是否第一次调用
			m_pInstance = new Singleton();
		return m_pInstance;
	}
};
Singleton * Singleton::m_pInstance = nullptr;

类Garbo被定义为Singleton的私有内嵌类,以防该类被在其他地方滥用。
程序运行结束时,系统会调用Singleton的静态成员garbo的析构函数,该析构函数会删除单例的唯一实例。
使用这种方法释放单例对象有以下特征:
在单例类内部定义专有的嵌套类;
在单例类内定义私有的专门用于释放的静态成员;
利用程序在结束时析构全局变量的特性,选择最终的释放时机;
使用单例的代码不需要任何操作,不必关心对象的释放。

3.饿汉式

使用局部静态变量,非常强大的方法,完全实现了单例的特性,而且代码量更少,也不用担心单例销毁的问题。

class Singleton {
private:
	Singleton() {} //构造函数是私有的
public:
	static Singleton & getInstance() {
		static Singleton instance = new Singleton(); //局部静态变量
		return instance;
	}
};

但是当以如下方法使用单例时问题来了:
Singleton singleton = Singleton::GetInstance();
这么做就出现了一个类拷贝的问题,这就违背了单例的特性。产生这个问题原因在于:编译器会为类生成一个默认的构造函数,来支持类的拷贝。
我们要禁止类拷贝和类赋值,禁止程序员用这种方式来使用单例:

class Singleton {
private:
	Singleton() {} //构造函数是私有的
	Singleton(const Singleton &);
	Singleton & operator = (const Singleton &);
public:
	static Singleton & GetInstance() {
		static Singleton instance = new Singleton(); //局部静态变量
		return instance;
	}
};

4.线程安全——双重检测锁定(Double Checked Locking)

class Singleton {
private:
	Singleton() {}  //构造函数是私有的
	static Singleton *instance;
public:
	static Singleton * GetInstance() {
            if(instance == nullptr) { //判断是否第一次调用
                lock();
                if(instance == nullptr)
                    instance = new Singleton();
                unlock();
            }
	    return instance;
	}
};
Singleton * Singleton::instance = nullptr;
 

DCL用于在多线程环境下保证单一创建Singleton对象。第一次check不用加锁,但是第二次check和创建对象必须加锁。由于编译器可能会优化代码,乱序执行,可能导致DCL失效。例如:

m_Instance = new Singleton(); 这个语句会分成三步完成:

(1)分配内存,

(2)在已经分配的内存上调用构造函数创建对象,

(3)将对象赋值给指针m_Instance .

但是这个顺序很可能会被改变为1,3,2。如果A线程在1,3执行完后,B线程执行第一个条件判断if(m_Instance ==0),此时锁不能起到保护作用。B线程会认为m_Instance 已经指向有效对象,可以去使用了。嘿嘿,灾难发生。

volatile对于执行顺序也没有帮助,解决不了DCL的问题。

5.线程安全——pthread_once

template
class Singleton : boost::noncopyable {
public:
    static T& getInstance() {
        pthread_once(&ponce_, &Singleton::init);
        return *value_;
    }
private:
    Singleton();
    ~Singleton();
    static void init() {
        value_ = new T();
    }
private:
    static pthread_once_t ponce_;
    static T*             value_;
};

template
pthread_once_t Singleton::ponce_ = PTHREAD_ONCE_INIT;

template
T * Singleton::value_ = nullptr;

参考《Linux多线程服务端编程》陈硕 2.5节。

使用pthread_once_t来保证线程安全。线程安全性由Pthreads库保证。

没有考虑对象的销毁,在长时间运行的服务器程序里,这不是一个问题,反正进程也不打算正常退出。在短期运行的程序中,程序退出时自然就释放所有资源了。

你可能感兴趣的:(C++,多线程,设计模式,linux)