单例模式与双重检查方案
2009-08-01 22:45
239 查看
本文链接:单例模式与双重检查方案
一个类在全局只有一个实例的应用方式,即为单例模式。单例模式一般应用在全局性的工具之类的类上,如一个程序中的各种文件管理器(这个有可能进化成多例模式),或者是一些工具类(其实一些简单的单例类,写成纯静态类都可以,效果差不多的-_-!)。实现方式主要是单例类自己维护一个静态的自己类对象的引用,而使用这个类的时候通过一个静态方法获得那唯一的一个实例。代码见后面的例子。
双重检查,为了提高性能而使用的一个小技巧。 以下面的例子为例。为了保证多线程中的调用安全性,对获得实例的方法GetInstance()内部进行了线程同步(lock(object))。但是锁的开销要比if语句大得多,因而有如下改进方案,首先检查instance是否为空,不为空的话就不用同步,直接返回instance,如果为空,进入加锁语句块,为了避免刚才检查instance之后有另一个线程创建了instance的实例,所以在同步块中再次检查,而这次可以确保不会有其他线程正执行在能创建instance的地方,如果的确有其他线程在之前短暂的时间内加了进来创建了instance,那么就退出同步块,返回已经创建的instance,否则自己创建。这样当instance已经实例化后,GetInstance()方法就不会进行线程同步。这就是所谓的双重检查。
单例模式有许多种具体的实现方式,这里是一种为了保证线程安全性而使用双重检查的实现。
C#代码如下: (Java几乎一样,除了关键字)
很完美吧?只有instance没有初始化之前才需要同步,instance一旦有了对象,就不会进入同步语句了,可以节省以后获得实例的时间。
不过,先别急,问题没有结束呢。
查看资料后,发现这里有一个问题,在.Net与Java中,这通常不能保证结果的正确!!(C++好像是可以的,不确定)
原因在于其执行顺序,instance = new Singleton();在.Net与Java中不保证构造在引用赋值之前执行!从而可能引用到未构造完成的对象。也就是说这样虽然能保证大多数情况下的正确,但不是全部!而如果因为这个地方造成了程序出错,估计你很难想到是哪里出问题了。
(网上这样的例子很多,详细的也不少,感兴趣的话大家肯以看看)
那就没有改进的办法了么?
先看这样:
把instance = new Singleton();改为:
Singleton temp = new Singleton();
instance = temp;
怎么样?这样是不是可以确保instance引用的一定是初始化过的对象?
看起来很完美。
实际上呢?别忘了编译器是干什么的。优化啊,优化。一优化,上面那两种写法不就一样了?
那么换成这样呢?
Singleton temp = new Singleton();
int i = 0;
i += 2;
instance = temp;
编译器的优化会做类似与这样的动作:为了提高执行速度,可能把代码前后没有逻辑关联的的语句调换位置。结果就不知道谁在谁前面执行了……而且i这个没有任何意义的变量相关动作会被优化掉,等于和上面的一样了。
总有办法的吧?应该有吧,大家都可以想想。我提供一个比较垃圾的方法(不保证正确,仅提供个参考,还请大家多提意见):
首先加一个Method:
private Singleton m(Singleton s) {
return s != null ? s : new Singleton(); // no use at all, 's' unable to be null.
}
然后把instance = new Singleton();改为:
Singleton temp = new Singleton();
instance = temp.m(temp);
这样的话,只要方法m不被内联,就可以保证instance的赋值绝对可以按照顺序执行。(我不知道.Net里面的自动内联机制是怎么处理的。Java不清楚。而且这样的方法,就算内联了,执行顺序应该也不会被优化乱了)
其实在.Net与Java里面有更好、更简单的、效率更高的单例模式的实现方法,看代码。推荐使用下面的方法。
C#代码: (这里为了与C++、Java更接近而使用了方法。在.Net中更推荐使用属性(property)来完成getter与setter,这样代码更简洁,使用更方便。)
怎么样,简单吧。而且连if语句都可以省掉了。和上面的单例模式实现方法相比,唯一的缺点在于这种单例模式的单例类只要一加载(.Net/Java),实例就被创建,而不是第一次用到的时候。
在C++中稍微麻烦些。
C++代码:
然后可以这样简单的测试下:
我想会有人问,只有new而没有delete?这不是内存泄漏么?这样没事吗?是的,是没有delete,也确实没事。
单例模式,单例嘛,整个内存中就这一个,泄漏也就这一个。而且单例模式的用意就是整个程序不论何时使用这个类,只有这一个实例,也就是要从头用到尾了,所以说这个实例占用的内存就不用管了,在程序退出的时候交给操作系统去做善后吧。
一个类在全局只有一个实例的应用方式,即为单例模式。单例模式一般应用在全局性的工具之类的类上,如一个程序中的各种文件管理器(这个有可能进化成多例模式),或者是一些工具类(其实一些简单的单例类,写成纯静态类都可以,效果差不多的-_-!)。实现方式主要是单例类自己维护一个静态的自己类对象的引用,而使用这个类的时候通过一个静态方法获得那唯一的一个实例。代码见后面的例子。
双重检查,为了提高性能而使用的一个小技巧。 以下面的例子为例。为了保证多线程中的调用安全性,对获得实例的方法GetInstance()内部进行了线程同步(lock(object))。但是锁的开销要比if语句大得多,因而有如下改进方案,首先检查instance是否为空,不为空的话就不用同步,直接返回instance,如果为空,进入加锁语句块,为了避免刚才检查instance之后有另一个线程创建了instance的实例,所以在同步块中再次检查,而这次可以确保不会有其他线程正执行在能创建instance的地方,如果的确有其他线程在之前短暂的时间内加了进来创建了instance,那么就退出同步块,返回已经创建的instance,否则自己创建。这样当instance已经实例化后,GetInstance()方法就不会进行线程同步。这就是所谓的双重检查。
单例模式有许多种具体的实现方式,这里是一种为了保证线程安全性而使用双重检查的实现。
C#代码如下: (Java几乎一样,除了关键字)
class Singleton { private static Singleton instance; private Singleton() { // your own code here } public static Singleton GetInstance() { // first check. if (instance == null) { // Notice, more than one threads might be here. lock (typeof(Singleton)) { // synchronize // second check after locked the object: class of Singleton. if (instance == null) { instance = new Singleton(); } } } return instance; } // other methods }
很完美吧?只有instance没有初始化之前才需要同步,instance一旦有了对象,就不会进入同步语句了,可以节省以后获得实例的时间。
不过,先别急,问题没有结束呢。
查看资料后,发现这里有一个问题,在.Net与Java中,这通常不能保证结果的正确!!(C++好像是可以的,不确定)
原因在于其执行顺序,instance = new Singleton();在.Net与Java中不保证构造在引用赋值之前执行!从而可能引用到未构造完成的对象。也就是说这样虽然能保证大多数情况下的正确,但不是全部!而如果因为这个地方造成了程序出错,估计你很难想到是哪里出问题了。
(网上这样的例子很多,详细的也不少,感兴趣的话大家肯以看看)
那就没有改进的办法了么?
先看这样:
把instance = new Singleton();改为:
Singleton temp = new Singleton();
instance = temp;
怎么样?这样是不是可以确保instance引用的一定是初始化过的对象?
看起来很完美。
实际上呢?别忘了编译器是干什么的。优化啊,优化。一优化,上面那两种写法不就一样了?
那么换成这样呢?
Singleton temp = new Singleton();
int i = 0;
i += 2;
instance = temp;
编译器的优化会做类似与这样的动作:为了提高执行速度,可能把代码前后没有逻辑关联的的语句调换位置。结果就不知道谁在谁前面执行了……而且i这个没有任何意义的变量相关动作会被优化掉,等于和上面的一样了。
总有办法的吧?应该有吧,大家都可以想想。我提供一个比较垃圾的方法(不保证正确,仅提供个参考,还请大家多提意见):
首先加一个Method:
private Singleton m(Singleton s) {
return s != null ? s : new Singleton(); // no use at all, 's' unable to be null.
}
然后把instance = new Singleton();改为:
Singleton temp = new Singleton();
instance = temp.m(temp);
这样的话,只要方法m不被内联,就可以保证instance的赋值绝对可以按照顺序执行。(我不知道.Net里面的自动内联机制是怎么处理的。Java不清楚。而且这样的方法,就算内联了,执行顺序应该也不会被优化乱了)
其实在.Net与Java里面有更好、更简单的、效率更高的单例模式的实现方法,看代码。推荐使用下面的方法。
C#代码: (这里为了与C++、Java更接近而使用了方法。在.Net中更推荐使用属性(property)来完成getter与setter,这样代码更简洁,使用更方便。)
class Singleton { private static Singleton instance = new Singleton(); private Singleton() { // your own code here } private static Singleton GetInstance() { return instance; } // other methods }
怎么样,简单吧。而且连if语句都可以省掉了。和上面的单例模式实现方法相比,唯一的缺点在于这种单例模式的单例类只要一加载(.Net/Java),实例就被创建,而不是第一次用到的时候。
在C++中稍微麻烦些。
C++代码:
class Singleton { static Singleton* instance; private: Singleton(); virtual ~Singleton(); // other methods public: static Singleton* GetInstancePtr(); static Singleton& GetInstanceRef(); }; Singleton* Singleton::instance = new Singleton(); Singleton::Singleton() { Console::WriteLine("In Singleton's Constructor."); } Singleton::~Singleton() { Console::WriteLine("In Singleton's Destructor."); } Singleton* Singleton::GetInstancePtr() { return instance; } Singleton& Singleton::GetInstanceRef() { return *instance; }
然后可以这样简单的测试下:
Singleton* instance1 = Singleton::GetInstancePtr(); Singleton& instance2 = Singleton::GetInstanceRef(); Console::WriteLine("instance1 == instance2 : " + (instance1 == &instance2));
我想会有人问,只有new而没有delete?这不是内存泄漏么?这样没事吗?是的,是没有delete,也确实没事。
单例模式,单例嘛,整个内存中就这一个,泄漏也就这一个。而且单例模式的用意就是整个程序不论何时使用这个类,只有这一个实例,也就是要从头用到尾了,所以说这个实例占用的内存就不用管了,在程序退出的时候交给操作系统去做善后吧。
相关文章推荐
- 双重检查模式单例续(DCL的代替方案)
- 单例模式中的 双重检查锁定
- C++11 双重检查锁定模式
- 双重检查锁定及单例模式
- Singleton(单例)模式和Double-Checked Locking(双重检查锁定)模式
- java 双重检查锁定及单例模式
- Java并发16:volatile关键字的两种用法-一次性状态标志、双重检查单例模式
- 利用双重检查加锁机制实现线程安全的单例模式
- 双重检查锁定及单例模式
- java 双重检查锁定及单例模式
- C++和双重检查锁定模式(DCLP)的风险
- java 双重检查模式
- 双重检查锁定及单例模式(ibm社区)
- 单例模式之双重检查加锁
- 双重检查锁定及单例模式
- 二十二、应用双重锁定检查于单例模式中的问题
- 单例模式-研磨设计模式--单例模式--双重检查加锁(一)
- 单例模式的双重检查成例的研究
- 单例模式双重检查锁(DCL)问题
- Java 单例模式中使用双重检查(Double-Check)