设计模式(7)——单例模式(Singleton Pattern,创建型)

1.概述

使用设计模式可以提高代码的可复用性、可扩充性和可维护性。单例模式(Singleton Pattern),确保一个类只有一个实例,并提供一个全局访问点。

有一些类对象我们只需要一个,比方说线程池(threadpool)、缓存(cache)、对话框、注册表(registry)、日志对象,充当打印机、显卡等设备的驱动程序的对象。此时,可以使用单例模式。

单例模式类图结构:

2.简单实现

单例模式可以直接用一个全局变量,但这样的代码不够优雅,而且不能保证只能创建一个对象,也就是说除了一个全局实例外,仍然可以创建多个类的实例。《设计模式》一书中给出了一种很不错的实现,定义一个单例类,使用类的私有静态指针变量指向类的唯一实例,并用一个公有静态方法获取该实例。即便如此,对具体实现的细节的不同处理,单例模式有多种不同的实现方式,多种写法各有利弊,下面请看C++单例模式。

(1)急切式

class Singleton {
private:
    Singleton(){}   //构造函数私有
    static Singleton pInstance;
public:
    static Singleton* getInstance(){
        return &pInstance;
    }
};
Singleton Singleton::pInstance;  //定义静态类对象

这种方式在进入main函数前就完成了类对象的定义,避免了多线程的同步问题,但是没有做到需要类对象时才定义,没有达到“懒惰实例化”的效果。

(2)懒惰式(线程不安全)

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

这种方式延迟了类对象的实例化,在需要的时候再实例化。但上面因为没有线程同步操作,所以存在多线程不安全隐患,多个线程同时调用getInstance()时可能会创建多个类对象。

(3)懒惰式(线程安全)

HANDLE hMutex;
//使用互斥对象迁,先创建互斥对象  
hMutex = CreateMutex( NULL,         //默认安全级别  
                      FALSE,        //创建它的线程不拥有互斥对象  
                      NULL);        //没有名字
class Singleton{
private:
    Singleton(){}   //构造函数私有
    static Singleton* pInstance;
public:
    static Singleton* getInstance(){
        WaitForSingleObject(hMutex, INFINITE);  //上锁
        if (pInstance == NULL)  //判断是否第一次调用  
            pInstance = new Singleton();
        ReleaseMutex(hMutex);   //解锁
        return pInstance;
    }
};
Singleton* Singleton::pInstance=NULL;

这种写法能够在多线程中很好的工作,但是每次调用getInstance方法时都需要进行同步,造成不必要的同步开销,而且只有第一次创建类对象时才需要同步,所以不建议用这种写法。

(4)双重检查加锁式(Double-checked Locking,DCL)

HANDLE hMutex;
hMutex = CreateMutex( NULL,FALSE,NULL);

class Singleton{
private:
    Singleton(){}   //构造函数私有
    static Singleton* pInstance;
public:
    static Singleton* getInstance(){
        if (pInstance == NULL){     //判断是否第一次调用  
            WaitForSingleObject(hMutex, INFINITE);  //上锁
            if (pInstance == NULL)  
                pInstance = new Singleton();
            ReleaseMutex(hMutex);   //解锁
        }
        return pInstance;
    }
};
Singleton* Singleton::pInstance=NULL;

这种写法在getInstance()方法中进行了两次判空,第一次为了不必要的同步,第二次是在pInstance等于NULL的情况下才创建实例,同步操作实际上只发生了一次,大大提高了效率。

(5)局部静态变量式

class Singleton{
private:
    Singleton(){}   //构造函数私有
public:
    static Singleton* getInstance(){
        static Singleton instance;
        return &instance;
    }
};

使用局部静态变量,非常巧妙的方法,完全实现了单例的特性,而且代码简洁优雅。

这里要注意一个问题,如果getInstance()函数返回的是类对象引用,会出现类拷贝的问题,这就违背了单例的特性。产生这个问题原因在于:编译器会为类生成一个默认的拷贝构造函数。例如下面的代码就会有问题:

Singleton singleton = Singleton::getInstance();//getInstance()返回对象引用

解决办法将默认拷贝构造函数申明为私有。

class Singleton{
private:
    Singleton(){}   //构造函数私有
    Singleton(const Singleton&)=delete; //禁止拷贝构造函数
public:
    static Singleton& getInstance(){
        static Singleton instance;
        return instance;
    }
};

3.小结

(1)单例模式,确保一个类只有一个实例,并提供一个全局访问点。 (2)在确保程序中某个类只有一个实例时,请采用单例模式,且推荐局部静态变量式写法。

有新的实现方法,会继续补充,欢迎网友留言指教!


参考文献

[1]C++中的单例模式 [2]设计模式(二)单例模式的七种写法 [3]Head First 设计模式(中文版)

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏JavaEE

mybatis的association以及collection的用法association:一对一关联(has one)collection:一对多关联(has many)

64080
来自专栏cloudskyme

设计模式(4)-序列生成器之单例模式

场景:序列生成器 系统中统一的序列生成程序,整个系统统一一套!那么就用单例模式吧! 首先看看单例模式 1)类持有一个自己的实例,而且还是个静态实例。 2)类的构...

34860
来自专栏机器学习原理

图数据库neo4j介绍(4)——常用语法

union:把多段match的return结果 上线组合一个结果集,会自动去掉重复行

11120
来自专栏跟着阿笨一起玩NET

sql server 获取每一个类别中值最大的一条数据

SELECT  * FROM    (           SELECT    * ,                     ROW_NUMBER() OVE...

40410
来自专栏机器学习入门

LWC 56:718. Maximum Length of Repeated Subarray

LWC 56:718. Maximum Length of Repeated Subarray 传送门:718. Maximum Length of Repea...

22060
来自专栏Clive的技术分享

PHP实现单例模式

<?php /** * 单例模式实现 */ class Singleton { //静态变量保存全局实例 private static $ins...

32570
来自专栏乐沙弥的世界

PL/SQL 集合的方法

    PL/SQL中提供了常用的三种集合联合数组、嵌套表、变长数组,而对于这几个集合类型中元素的操作,PL/SQL提供了相应的函数或过程来操 纵数组中的元素...

8630
来自专栏C/C++基础

C++ Hash表模板

利用C++类模板实现任意类型的Hash表,提供的功能有: (1)指定shmkey或内存地址创建Hash表; (2)获取指定key元素; (3)遍历指...

55440
来自专栏Java后端技术

你敢说自己了解单例模式?

  最近在学习设计模式,在看到单例模式的时候,我一开始以为直接很了解单例模式了,实现起来也很简单,但是实际上单例模式有着好几个变种,并且多线程中涉及到线程安全问...

11020
来自专栏北京马哥教育

SQL函数汇总【精选篇】

1.绝对值 SQL:select abs(-1) value O:select abs(-1) value from dual 2.取整(大) ...

32890

扫码关注云+社区

领取腾讯云代金券