单例模式可以保证一个类有且只有一个实例,并提供一个访问它的全局访问点.在程序设计中,有很多情况需要确保一个类只能有一个实例.从这句话可以 看出,Singleton模式的核心:如何控制用户使用new对一个类的实例构造器的任意调用。如何绕过常规的构造器,提供一种机制来保证一个类只有一个 实例?这应该是类设计者的责任,而不是使用者的责任。
一、单例模式意图
保证一个类有且只有一个实例,并提供一个访问它的全局访问点。
二、单例模式UML图
图1: Singleton 模式结构
实现 Singleton 模式的办法通常有三种:
1.用静态方法实现 Singleton;
2.以静态变量为标志实现 Singleton;
3.用注册器机制来创建 Singleton。
三 实现Java Singleton的方案
Java Singleton是指在特定系统范围内只能实例化一次的Java类,如何理解特定系统范围?按照需求和环境定义系统范围,关注在特定系统范围内单一实例的需求:
系统范围和环境定义 | 方案编号 |
框架容器内 | A |
单一JVM中、单一类加载器加载类 | B、C、D |
单一JVM、不同类加载器加载相同类 | |
系统跨多个JVM |
A
提供实例管理容器的第三方框架,例如Spring IOC容器,可以通过配置保证实例在容器内的唯一性。这些被外部容器管理的类,能在某个容器范围内达到Singleton的效果,不一定禁止自身在容器外 部范围生成多个实例。 这种方案可以看作是一种局部的单例解决方案。
B
代码示范如下
C
D
有时候类的实例化开销较为昂贵,有时候类的实例化要用到系统运行时的动态数据做参数等等,在上述情形,静态成员变量需要延迟初始化。程序员中间流传较广的一种形式是:
这种写法最大的问题是getInstance()方法被多个线程竞争使用的时候,可能会产生多个实例,违反了单例设计的初衷。如果多个实例的风险(Singleton失败)不会引起您的系统异常,比如实例存放的是无状态的数据、实例是轻量级的,您可以坚持这种写法。
当然还有改进的方法,一般是在该方法前加上“synchronized”关键字:
这种作法的副作用就是影响性能。
双检查锁(Double-Checked Locking)是在多线程环境下实现延迟初始化的有效方式(如C++),不幸的是,对大多数JVM是无效的。有一篇文章解释的很深入:http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
其他方案
单一JVM、不同类加载器加载相同类或跨多个JVM的情形,要保证Java Singleton不失败,我还没找到恰当的方法。
有些人认为单例的需求不仅仅是来自某些组件自身的唯一特性,还来自对创建并维护多个对象产生消耗的无法忍受、来自对性能的追求。如果出于降低消耗和 提高性能的目的,很多无状态的类(类的所有实例天然是始终相同的),即便面临单一JVM、不同类加载器加载相同类或跨多个JVM的情形,也可以采用上述的 Singleton实现方式,可以尽量减少实例的数量。
当Java Singleton遇到反序列化
一个序列化的实例,每次反序列化的时候都会产生一个新的实例。Singleton也不例外。
我们看看一个例子:
进行序列化测试
public static void main(String[] args) throws Exception{
//序列化
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("E://kingBedroom.obj"));
King king_1 = King.INSTANCE;
objectOutputStream.writeObject(king_1);
objectOutputStream.close();
//反序列化
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("E://kingBedroom.obj"));
King king_2 = (King)objectInputStream.readObject();
objectInputStream.close();
//比较是否原来的实例
System.out.println(king_1==king_2);
}
结果是false
解决方法是为King类增加readResolve()方法:
private Object readResolve(){
return INSTANCE;
}
反序列化之后新创建的对象会先调用此方法,该方法返回的对象引用被返回,取代了新创建的对象。本质上,该方法忽略了新建对象,仍然返回类初始化时创建的那个实例。
五:C++的设计与实现:
C++完美实现Singleton模式
Singleton 模式是常用的设计模式之一,但是要实现一个真正实用的设计模式却也不是件容易的事情。
1. 标准的实现
class Singleton
{
public:
static Singleton * Instance()
{
if( 0 == _instance)
{
_instance = new Singleton;
}
return _instance;
}
protected:
Singleton(void)
{
}
virtual ~ Singleton(void)
{
}
static Singleton* _instance;
};
这是教科书上使用的方法。看起来没有什么问题,其实包含很多的问题。下面我们一个一个的解决。
2. 自动垃圾回收
上面的程序必须记住在程序结束的时候,释放内存。为了让它自动的释放内存,我们引入 auto_ptr 改变它。
#include <memory>
#include <iostream>
using namespace std;
class Singleton
{
public:
static Singleton * Instance()
{
if( 0 == _instance. get())
{
_instance. reset( new Singleton);
}
return _instance. get();
}
protected:
Singleton(void)
{
cout << "Create Singleton" << endl;
}
virtual ~ Singleton(void)
{
cout << "Destroy Singleton" << endl;
}
friend class auto_ptr< Singleton>;
static auto_ptr< Singleton> _instance;
};
//Singleton.cpp
auto_ptr< Singleton> Singleton:: _instance;
3. 增加模板
在我的一个工程中,有多个的 Singleton 类,对 Singleton 类,我都要实现上面这一切,这让我觉得烦死了。于是我想到了模板来完成这些重复的工作。
现在我们要添加本文中最吸引人单件实现:
#pragma once
#include <memory>
using namespace std;
using namespace C2217:: Win32;
namespace C2217
{
namespace Pattern
{
template <class T>
class Singleton
{
public:
static inline T* instance();
private:
Singleton(void){}
~ Singleton(void){}
Singleton(const Singleton&){}
Singleton & operator= (const Singleton &){}
static auto_ptr< T> _instance;
};
template <class T>
auto_ptr< T> Singleton< T>:: _instance;
template <class T>
inline T* Singleton< T>:: instance()
{
if( 0 == _instance. get())
{
_instance. reset ( new T);
}
return _instance. get();
}
//Class that will implement the singleton mode,
//must use the macro in it's delare file
#define DECLARE_SINGLETON_CLASS( type ) /
friend class auto_ptr< type >; /
friend class Singleton< type >;
}
}
4. 线程安全
上面的程序可以适应单线程的程序。但是如果把它用到多线程的程序就会发生问题。主要的问题在于同时执行 _instance. reset ( new T); 就会同时产生两个新的对象,然后马上释放一个,这跟 Singleton 模式的本意不符。所以,你需要更加安全的版本:
#pragma once
#include <memory>
using namespace std;
#include "Interlocked.h"
using namespace C2217:: Win32;
namespace C2217
{
namespace Pattern
{
template <class T>
class Singleton
{
public:
static inline T* instance();
private:
Singleton(void){}
~ Singleton(void){}
Singleton(const Singleton&){}
Singleton & operator= (const Singleton &){}
static auto_ptr< T> _instance;
static CResGuard _rs;
};
template <class T>
auto_ptr< T> Singleton< T>:: _instance;
template <class T>
CResGuard Singleton< T>:: _rs;
template <class T>
inline T* Singleton< T>:: instance()
{
if( 0 == _instance. get() )
{
CResGuard:: CGuard gd( _rs);
if( 0 == _instance. get())
{
_instance. reset ( new T);
}
}
return _instance. get();
}
//Class that will implement the singleton mode,
//must use the macro in it's delare file
#define DECLARE_SINGLETON_CLASS( type ) /
friend class auto_ptr< type >; /
friend class Singleton< type >;
}
}
CresGuard 类主要的功能是线程访问同步 , 代码如下:
#pragma once
///
// Instances of this class will be accessed by multiple threads. So,
// all members of this class (except the constructor and destructor)
// must be thread-safe.
class CResGuard {
public:
CResGuard() { m_lGrdCnt = 0 ; InitializeCriticalSection(& m_cs); }
~ CResGuard() { DeleteCriticalSection(& m_cs); }
// IsGuarded is used for debugging
BOOL IsGuarded() const { return( m_lGrdCnt > 0 ); }
public:
class CGuard {
public:
CGuard( CResGuard& rg) : m_rg( rg) { m_rg. Guard(); };
~ CGuard() { m_rg. Unguard(); }
private:
CResGuard& m_rg;
};
private:
void Guard() { EnterCriticalSection(& m_cs); m_lGrdCnt++; }
void Unguard() { m_lGrdCnt--; LeaveCriticalSection(& m_cs); }
// Guard/Unguard can only be accessed by the nested CGuard class.
friend class CResGuard:: CGuard;
private:
CRITICAL_SECTION m_cs;
long m_lGrdCnt; // # of EnterCriticalSection calls
};
///
5. 实用方法
比如你有一个需要实现单件模式的类,就应该这样实现:
#pragma once
#include "singleton.h"
using namespace C2217:: Pattern;
class ServiceManger
{
public:
void Run()
{
}
private:
ServiceManger(void)
{
}
virtual ~ ServiceManger(void)
{
}
DECLARE_SINGLETON_CLASS( ServiceManger);
};
typedef Singleton< ServiceManger> SSManger;
在使用的时候很简单,跟一般的 Singleton 实现的方法没有什么不同。
int _tmain(int argc, _TCHAR* argv[])
{
SSManger:: instance()-> Run();
}