单例模式

Posted qiu00

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了单例模式相关的知识,希望对你有一定的参考价值。

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

  要定义一个单例模式的类,首先将其构造函数私有化,以防止外界通过其创建对象。因为单例类仅有一个实例,所以创建一个类的私有静态指针变量,使其指向类的唯一实例。要使外界能够获取到私有静态指针变量指向的实例,所以要创建一个公有的静态函数

  1、懒汉版

 1 class Singleton
 2 {
 3     private:
 4         //构造函数私有化
 5         Singleton() {};
 6         ~Singleton() {};
 7         Singleton(const Singleton&);
 8         Singleton& operator=(const Singleton&);
 9         
10     private:
11         //指向唯一实例的私有静态指针变量
12         static Singleton *instance;
13         
14     public:
15         //公有静态函数
16         static Singleton* getInstance()
17         {
18             if (instance == NULL) 
19             {
20                 instance = new Singleton();
21             }
22             return instance;
23         }
24 };
25 
26 Singleton *Singleton::instance = NULL;

   这个版本在单线程下是没有问题的,但是如果是多线程使用的话是不安全的。假设有两个线程调用getInstance(),instance=NULL,1线程执行到20行代码,此时2线程获的时间片,2线程同样会执行20行代码,这样就造成了线程的不安全。

  2、加锁版

  

class Singleton
{
    private:
        //构造函数私有化
        Singleton() {};
        ~Singleton() {};
        Singleton(const Singleton&);
        Singleton& operator=(const Singleton&);
        
    private:
        //指向唯一实例的私有静态指针变量
        static Singleton *instance;
        
    public:
        //公有静态函数
        static Singleton* getInstance()
        {
            Lock lock;
            if (instance == NULL) 
            {
                instance = new Singleton();
            }
            return instance;
        }
};

Singleton *Singleton::instance = NULL;

  这样的是线程安全的,但是锁的使用会降低性能,如果是web高并发下,这种版本效率太低。

  3、双检查锁版

 1 class Singleton
 2 {
 3     private:
 4         //构造函数私有化
 5         Singleton() {};
 6         ~Singleton() {};
 7         Singleton(const Singleton&);
 8         Singleton& operator=(const Singleton&);
 9         
10     private:
11         //指向唯一实例的私有静态指针变量
12         static Singleton *instance;
13         
14     public:
15         //公有静态函数
16         static Singleton* getInstance()
17         {
18             if(instance == NULL)
19             {
20                 //基于作用域加锁
21                 Lock lock;
22                 if (instance == NULL) 
23                 {
24                     instance = new Singleton();
25                 }
26             }//超出作用域解锁
27             return instance;
28         }
29 };
30 
31 Singleton *Singleton::instance = NULL;

   这个版本下,按前面所假设情况,2线程会阻塞在21行,直到1线程执行完毕,但是这个版本还是有问题。在24行,我们通常所认为的指令执行顺序应该是:先创建内存,然后构造器构造,最后将内存地址返回,但是很多编译器会对程序进行优化,优化后的指令执行顺序有可能会是:先创建内存,然后将内存地址返回,最后构造器构造。这样的话有可能2线程在18行判断instance!=NULL,直接返回instance,然后直接使用instance指向的内存中的数据,但是内存中可能没有数据,此时程序就出现了错误。

  解决办法:

 1 class Singleton
 2 {
 3     private:
 4         //构造函数私有化
 5         Singleton() {};
 6         ~Singleton() {};
 7         Singleton(const Singleton&);
 8         Singleton& operator=(const Singleton&);
 9         
10     private:
11         //指向唯一实例的私有静态指针变量
12         static std::atomic<Singleton *> m_instance;
13         static std::mutex m_mutex;
14         
15     public:
16         //公有静态函数
17         static Singleton* getInstance()
18         {
19             Singleton *tmp= m_instance.load(std::memory_order_relaxed);
20             //获取内存fence,tmp不被编译器优化
21             std::atomic_thread_fence(std::memory_order_acquire);
22             if(tmp==nullptr)
23             {
24                 std::lock_guard<std::mutex> lock(m_mutex);
25                 tmp= m_instance.load(std::memory_order_relaxed);
26                 if(tmp==nullptr)
27                 {
28                     tmp= new Singleton;
29                     //释放fence
30                     std::atomic_thread_fence(std::memory_order_release);
31                     m_instance.store(tmp, std::memory_order_relaxed);
32                 }
33             }
34             return tmp;
35         }
36 };

 

以上是关于单例模式的主要内容,如果未能解决你的问题,请参考以下文章

常用代码片段

性能比较好的单例写法

片段作为 Android 中的单例

单例片段或保存网页视图状态

你熟悉的设计模式都有哪些?写出单例模式的实现代码

单例模式以及静态代码块