单例模式(Singleton Pattern)

198 阅读3分钟

单例模式

单例模式(Singleton Pattern) —— 保证一个类仅有一个实例,并提供一个访问它的全局访问点。

何为单例模式,在GOF的《设计模式:可复用面向对象软件的基础》中是这样说的:保证一个类只有一个实例,并提供一个访问它的全局访问点。首先,需要保证一个类只有一个实例;在类中,要构造一个实例,就必须调用类的构造函数,如此,为了防止在外部调用类的构造函数而构造实例,需要将构造函数的访问权限标记为protected或private;最后,需要提供要给全局访问点,就需要在类中定义一个static函数,返回在类内部唯一构造的实例。

单例模式的结构是设计模式中最简单的,但实现中还是需要注意线程安全的问题。单例模式的应用场景:有一些对象其实只需要一个,比如:线程池,日志对象等。这些对象只能够拥有一个实例,如果创建出了多个实例,就会导致一些程序的问题,因此从对象的创建层面就加以限制,防止出错。

代码实现(C++)

方式一(经典实现):

#include<iostream>
#include<cassert>
using namespace std;

class Singleton
{
public:
	static Singleton* Instance();
	void update() {
        this->data++;
        cout << this->data << endl;
    }

private:
	Singleton() {}
	static Singleton* _instance;
    void set_data(int value) {
        this->data = value;
    }
    int data;
};

Singleton* Singleton::_instance = nullptr;

Singleton* Singleton::Instance() {
	if(nullptr == _instance) {
		_instance = new Singleton();
        _instance->set_data(0);
	}

	return _instance;
}

int main() {
	Singleton* sgt1 = Singleton::Instance();
    sgt1->update();
    Singleton* sgt2 = Singleton::Instance();
    sgt2->update();
    assert(sgt1 == sgt2);

    return 0;
}

这是最简单,也是最普遍的实现方式。但是,这种实现方式,有很多问题,比如:没有考虑到多线程的问题,在多线程的情况下,可能创建多个Singleton实例。以下版本是改善的版本。

方式二(线程安全实现):

#include<iostream>
#include<cassert>
#include<mutex>
using namespace std;

class Singleton
{
public:
	static Singleton* Instance();
	void update() {
        this->data++;
        cout << this->data << endl;
    }

private:
	Singleton() {}
	static Singleton* _instance;
    static mutex _mutex;
    void set_data(int value) {
        this->data = value;
    }
    int data;
};

Singleton* Singleton::_instance = nullptr;
mutex Singleton::_mutex;

Singleton* Singleton::Instance() {
	if (nullptr == _instance) {
		_mutex.lock();
        if (nullptr == _instance) {
            _instance = new Singleton();
            _instance->set_data(0);
        }
        _mutex.unlock();
	}

	return _instance;
}

int main() {
	Singleton* sgt1 = Singleton::Instance();
    sgt1->update();
    Singleton* sgt2 = Singleton::Instance();
    sgt2->update();
    assert(sgt1 == sgt2);

    return 0;
}

此处进行了两次_instance == nullptr的判断,使用的所谓的“双检锁”机制。因为进行一次加锁和解锁是需要付出对应的代价的,而进行两次判断,就可以避免多次加锁与解锁操作,同时也保证了线程安全。但是有不加锁的实现方式吗?为此,有下面两种单例模式实现方式:

方式三:

#include<iostream>
#include<cassert>
using namespace std;

class Singleton
{
public:
	static Singleton* Instance();
	void update() {
        this->data++;
        cout << this->data << endl;
    }

private:
	Singleton() {}
    Singleton(int v) {
        this->data = v;
    }

	static Singleton* _instance;
    void set_data(int value) {
        this->data = value;
    }
    int data;
};

Singleton* Singleton::_instance = new Singleton(0);

Singleton* Singleton::Instance() {
	return _instance;
}

int main() {
	Singleton* sgt1 = Singleton::Instance();
    sgt1->update();
    Singleton* sgt2 = Singleton::Instance();
    sgt2->update();
    assert(sgt1 == sgt2);

    return 0;
}

因为静态初始化在程序开始时,也就是进入主函数之前,由主线程以单线程方式完成了初始化,所以静态初始化实例保证了线程安全性。在性能要求比较高时,就可以使用这种方式,从而避免频繁的加锁和解锁造成的资源浪费。

方式四(静态局部变量实现):(推荐)

#pragma once

class Singleton
{
public:
	static Singleton* Instance();
	~Singleton();

private:
	Singleton();
};
#include<iostream>
#include<cassert>
using namespace std;

class Singleton
{
public:
	static Singleton* Instance();
	void update() {
        this->data++;
        cout << this->data << endl;
    }

private:
	Singleton() {}
    Singleton(int v) {
        this->data = v;
    }
    void set_data(int value) {
        this->data = value;
    }
    int data;
};


Singleton* Singleton::Instance() {
	static Singleton s(0);

	return &s;
}

int main() {
	Singleton* sgt1 = Singleton::Instance();
    sgt1->update();
    Singleton* sgt2 = Singleton::Instance();
    sgt2->update();
    assert(sgt1 == sgt2);

    return 0;
}

其实是使用了C++中成员函数的静态变量的特点:静态局部变量在第一次使用时初始化,并不会销毁直到程序退出。个人,一般情况下比较喜欢这种实现。

关注微信公众号,好好学习,天天向上!