本文将介绍单例模式的一些常见隐藏错误,一是C#中单例模式的线程安全问题,二是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;
}
}
}
}
这种方式的缺点就是在大量并发的时候,运行的效率不好。这两种方法可以根据实际的项目需求和设计进行选用。
在谈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,继续充电吧。