【模式】单件模式及其多线程版本

来源:互联网 发布:rpg游戏制作教程java 编辑:程序博客网 时间:2024/05/16 10:08
很多情况下需要将我们编写的程序中的类在一定范围内只保留一个实例,例如出于性能考虑,我们不希望反复实例化这个对象,用完了后再将它销毁,然后又用到这个类型的某些服务,再实例化,用完了再销毁,如此反复,不如做个全局的,实例化后,一直用到本程序功能结束,但是全局的又有一些问题。
        经典的Singleton模式可以为我们提供一个简单的解决方案,他提供了一个全局访问点,用来访问这个唯一实例,简单思想是:当外界想要持有这个类型的实例时,这个类型会自己检查这个实例是否已经被创建,如果没有则创建一个并返回这个实例的指针/引用,有了,则简单的返回这个对象的指针/引用即可。而且为了确保该类型的实例的唯一性,将构造函数protected或private。

        单件模式的示例代码如下:
class Singleton
...{
public:
         
static Singleton* GetInstance();
protected:
          Singleton();
private:
         
static Singleton*  m_pInstance;
}
;

Singleton
* Singleton::m_pInstance= 0;

Singleton
* Singleton::GetInstance()
...{
           
if(m_pInstance== 0)
           
...{
                      m_pInstance
= new Singleton;
            }

           
return m_pInstance;
}
        使用时,仅需要通过GetInstance成员函数访问这个单件,然后访问这个类型的其他服务,这样做最直接的好处就是避免了全局变量带来的污染命名空间的问题,同时也避免了全局对象的过早实例化的问题(因为全局对象会在main执行前处理,所以不论我们的程序需不需要访问这个类型,他都被实例化了,用singleton模式可以将他的实例化推迟到必需时)。

        单件模式还可以与工厂模式连用,即可以在GetInstance中判断一下需要的到底是那一种单件,然后用一个子类的对象返回给一个基类的指针就可以了。不过这些场模式的实例必须遵循单件模式。
         当然原型模式也可以连用,我之前有一篇blog介绍了我学mfc的时候做的一个简单的画图程序,当时用到了原型模式和场模式,里面的画笔和画刷其实可以做成单件模式,这样就不必在用户每次切换画笔或画刷的时候new一个pen或者brush了。

         上面说的情况没有考虑到线程问题,即如果存在多个线程要访问我们这个类的那个实例,就会有这样、那样的问题:
         1.如果第一个线程访问这个类时,m_pInstance == 0为true,那么他要new一个Singleton的对象,恰巧第二个线程也访问这个类,这是还没有new 这个类的对象,而且m_pInstance也是空,那么他也会new一个Singleton的对象,这就有问题,明明创建了两个对象么!析构时怎么办?只能内存泄露吧?
         2.由于对象是唯一的,如果这个Singleton有一个方法是取得一组值,另一个方法是遍历这组值,不加锁的情况下,可能会在第一个线程遍历到那组值的中间的时候,另一个线程开始遍历,在这样比较背的情况下,你往下遍历一个,我往下遍历一个,就乱套了~
         ......

        解决办法就是在m_pInstance上加锁,那么类似第二种情况的问题就可以解决,但是第一种情况呢?我们需要做的可能就是对“检查Singleton对象已经被创建”进行同步。这么做挺简单,不过会造成一个性能上的瓶颈,所有线程都必需等待检查对象是否存在。也许这个对象已经早在800年前就创建了,不过还得锁上-检查-解锁,浪费资源。另一种做法就是被称为双重检查-锁定的模式Doublechecked Locking!其意图就是将上面的那些已经存在对象的情况的锁上-检查-解锁的步骤省略掉。因此不会造成系统的瓶颈,问题只出现在第一次创建对象的时候,那么就把他提取出来好了。示例代码如下:
Singleton* Singleton::GetInstance()
...{
           
if(m_pInstance== 0)
           
...{
                     
//在这里同步,加锁
                     if(m_pInstance== 0)
                     
...{
                             m_pInstance
= new Singleton;
                      }
                    
            }

           
return m_pInstance;
}
        外层的if判断检查了已经存在实例的情况,内层的if判断则是上面所描述的第一种情况的检查。不过可惜,目前C++标准还没有引入线程方面的库,不知道boost中有没有。