设计模式

单例模式(Singleton)

2017-04-12  本文已影响31人  superLiS

前言

单例模式:用来创建独一无二的,只能够有一个实例的对象。个人认为,这才是最核心的一点,脱离了这一核心,可能就是滥用单例模式了。 只要核心是这一点,可根据具体应用场景的需要,选择单例模式的不同实现形式。不同的开发语言(C++,Java,Python......),对单例模式的实现也有细微不同,但其思想核心是一致的。

什么是单例模式

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

单例模式的应用

单例模式的结构是设计模式中最简单的,但是想要完全实现一个线程安全的单例模式还是有很多陷阱的。单例模式的应用场景:有一些对象其实只需要一个,比如:线程池,缓存,对话框,处理偏好设置和注册表的对象,日志对象,充当打印机,显卡等设备的驱动程序对象。这些对象只能够拥有一个实例,如果创建出了多个实例,就会导致一些程序的问题。程序的行为异常,资源使用的过量,或者导致不一致的结果。常用来管理共享的资源,比如数据库的连接或者线程池。

C++代码实现

方式一(经典懒汉实现):

#pragma once
class Singleton
{
public:
    static Singleton* Instance();
    ~Singleton();
private:
    Singleton();
    static Singleton* _instance;
};
#include "stdafx.h"
#include<iostream>
#include "Singleton.h"

Singleton* Singleton::_instance = nullptr;
Singleton::Singleton()
{
}

Singleton * Singleton::Instance()
{
    if(nullptr==_instance)
    {
        _instance = new Singleton();
    }
    return _instance;
}

Singleton::~Singleton()
{
}
#include "stdafx.h"
#include"Singleton.h"
int main()
{
    Singleton* sgt = Singleton::Instance();
    return 0;
}

这是最简单,也是最普遍的实现方式。但是,这种实现方式,有很多问题,比如:没有考虑到多线程的问题,在多线程的情况下,就可能创建多个Singleton实例。(可以想一下在多线程情况下,产生多个Singleton实例的问题,结合C++对象模型,对象内存布局来思考?)
以下版本是改善的版本。

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

#pragma once
#include<mutex>
class Singleton
{
public:
    static Singleton* Instance();
    ~Singleton();

private:
    Singleton();
    static Singleton* _instance;
    static std::mutex _mutex;
};
#include "stdafx.h"
#include<iostream>
#include "Singleton.h"

Singleton* Singleton::_instance = nullptr;
std::mutex Singleton::_mutex;
Singleton::Singleton()
{
}

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

    return _instance;
}

Singleton::~Singleton()
{
}
#include "stdafx.h"
#include"Singleton.h"

int main()
{
    Singleton* sgt = Singleton::Instance();
    return 0;
}

此处进行了两次_instance == nullptr的判断,使用的所谓的“双检锁”机制。因为进行一次加锁和解锁是需要付出对应的代价的,而进行两次判断,就可以避免多次加锁与解锁操作,同时也保证了线程安全。但是,这种实现方法在平时的项目开发中用的很好,也没有什么问题?但是应当注意加锁操作将成为一个性能的瓶颈;为此,一种新的单例模式的实现也就出现了。

方式三(饿汉实现):

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

private:
    Singleton();
    static Singleton* _instance;
};
#include "stdafx.h"
#include<iostream>
#include "Singleton.h"

Singleton* Singleton::_instance = new Singleton();
Singleton::Singleton()
{
}

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

Singleton::~Singleton()
{
}
#include "stdafx.h"
#include"Singleton.h"

int main()
{
    Singleton* sgt = Singleton::Instance();
    return 0;
}

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

方式四(静态局部变量懒汉实现):

#pragma once

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

private:
    Singleton();
};
#include "stdafx.h"
#include<iostream>
#include "Singleton.h"

Singleton::Singleton()
{
}

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

Singleton::~Singleton()
{
}
#include "stdafx.h"
#include"Singleton.h"

int main()
{
    Singleton* sgt = Singleton::Instance();
    return 0;
}

其实是使用了C++中成员函数的静态变量的特点:静态局部变量在第一次使用时初始化,并不会销毁直到程序退出。

关于懒汉饿汉

可以这样简单粗暴的理解:

上一篇 下一篇

猜你喜欢

热点阅读