单例模式之学习体会

本文将介绍单例模式的一些常见隐藏错误,一是C#中单例模式的线程安全问题,二是C++中单例模式的释放问题。

1、C#单例模式线程安全

我们先看一个常见的单例模式实例。

public sealed class Singleton
{
    static Singleton instance=null;
    Singleton()
    {
    }
    public static Singleton GetInstance()
    {
        if(instance==null)
        {
           instance = new Singleton();
        }
        return instance;
    }
}

看起来似乎没有什么问题,构造函数私有的,从而防止被实例化;通过共有静态方法GetInstance返回实例。然而,上面的代码不是线程安全的,比如A、B两个线程同时进入if判断语句内,发现instance还未被创建,于是实例化了两次,很明显这不符合单例模式的特点。

         我们需要做一些处理保证单例模式的线程安全。

方法一:变量声明的时候进行初始化

public sealed class Singleton
{
    static Singleton instance= new Singleton();
    Singleton()
    {
    }
    public static Singleton GetInstance()
    {
        return instance;
    }
}

         显然这样就不会发生实例化两次的问题,但它的缺点就是该类加载的时候就会直接new 一个静态对象出来,当系统中这样的类较多时,会使得启动速度变慢,不符合“延时加载”的特性。

方法二:加锁的方式

public sealedclass Singleton
{
    static Singleton instance=null;
    static object synObj = new object();
    Singleton()
    {
    }
    public static Singleton Instance
     {
        get
         {
            lock (synObj)
             {
                if (instance==null)
                 {
                    instance = new Singleton();
                }
                return instance;
            }
        }
    }
}

这种方式的缺点就是在大量并发的时候,运行的效率不好。这两种方法可以根据实际的项目需求和设计进行选用。

2、C++单例模式资源释放

在谈C++单例资源释放前,先说说C#的资源释放吧。在程序退出的时候,由GC自动回收程序资源,做个测试来证实一下。

	class A
        {
            static A a;
           
            static public A GetA()
            {
                if(null==a)
                    a= new A();
 
                return a;
            }
 
            A() { }
 
            ~A()
            {
                for (int i = 0; i < 10000;i++)
                {
                    Console.WriteLine("1");
                }     
            }
 
        }
 
        static void Main()
        {
            A a = A.GetA();
        }
 
  

看看运行的结果,下图是我在程序退出时候的截图。

说明程序退出的时候执行了析构函数,C#的垃圾回收机制很好帮我们解决了这一问题。再来看看C++的吧,我们先仿照C#写一个单例模式的实例。

class Singleton
{
public:
    static Singleton* Instance()
    {
        if(NULL==singleton)
               singleton = newSingleton();
 
        return singleton;
    }
 
     ~Singleton()
     {
          for(int i =0;i<10000;i++)
             cout<<"1";
     }
 
private:
    static Singleton* singleton;
    Singleton() { };
 
};
 
Singleton*Singleton::singleton=NULL;//初始化
 
void main()
{
    Singleton* a = Singleton::Instance();
}

想知道程序退出的时候,会不会调用析构函数,运行一下看看,结果我就不贴了,当然是析构函数没有执行,熟悉C++的朋友很容易理解。当然我们在main函数添加delete a,结果就正确了,是不是这样就万无一失了呢?我们知道单例模式的用途,谁也不能保证你delete之后就不会再调用,而且程序如果经过多次交接,这个地方就是一个很大的隐患,尤其是当我们的析构函数内还有其它的资源需要手动释放的时候。有人会问,我程序退出之后,电脑资源都回来了啊,那是当然啦,操作系统帮了你,作为一个程序员,如果你一直抱着程序崩溃之后,重启电脑、软件,我想明显不合适。因此,我们要找一个方法,让程序自己再退出的时候执行析构函数。

我先贴出一个明显的错误实例,而且这种错误很容易犯

class Singleton
{
public:
    static Singleton* Instance()
    {
        if(NULL==singleton)
            singleton = newSingleton();
 
        return singleton;
    }
 
    ~Singleton()
     {
          //for(int i=0;i<10000;i++)
          //      cout<<"1";
          if(NULL!=singleton)
              delete singleton;
         }
private:
    static Singleton* singleton;
    Singleton() { };

};

如果这样做的话,只会陷入一个死循环,因为delete释放就是调用类的析构函数,这不就是一个递归了么。

网上给出了三种方法,一一给大家展示

1、使用内嵌类辅助释放

class Singleton
{
public:
    static Singleton* Instance()
    {
                  
        if(NULL==singleton)
             singleton= new Singleton();
 
        return singleton;
    }
 
     ~Singleton()
      {
          for(int i=0;i<10000;i++)
              cout<<"1";
      }
 
private:
    static Singleton* singleton;
    Singleton() { };
         class CGarbo // 它的唯一工作就是在析构函数中删除CSingleton的实例  
    {   
    public:   
        ~CGarbo()   
        {     
            if(Singleton::singleton)   
                deleteSingleton::singleton;   
        }   
    };   
 
    static CGarbo Garbo; // 定义一个静态成员,在程序结束时,系统会调用它的析构函数    
};
 
Singleton *Singleton::singleton=NULL;//初始化
Singleton::CGarbo Singleton::Garbo;//这里必须加上
 
void main()
{
         Singleton* a  = Singleton::Instance();  
}

运行结果:

2、使用类静态成员变量实现

class Singleton
{
public:
    static Singleton* Instance()
    {                
        return &singleton;
    }
 
     ~Singleton()
     {
        for(int i=0;i<10000;i++)
           cout<<"1";
     }
 
private:
     static Singletonsingleton;
     Singleton() { };
};
 
Singleton Singleton::singleton;//初始化
void main()
{
     Singleton* a  = Singleton::Instance();  
}

3、使用局部静态变量实现

class Singleton
{
public:
    static Singleton*Instance()
    {                
        static Singleton singleton;
        return &singleton;
    }
 
    ~Singleton()
    {
       for(int i=0;i<10000;i++)
          cout<<"1";
    }
 
private:
     Singleton() { };
 
};
void main()
{
     Singleton* a =Singleton::Instance();      
}

其实第三种方法和第二种方法的原理是一样的,但是第二种方法在程序运行的时候就加载创建实例,而第三种方法则是“延时加载”,如何选取看程序的需要了。

看到这里,大家基本对单例模式有一个差不多的了解吧。再看看这篇文章吧,http://www.cnblogs.com/loveis715/archive/2012/07/18/2598409.html,继续充电吧。

你可能感兴趣的:(.NET,C++)