一、概述
在很多情况下,我们的系统只允许某个类有一个或指定个数的实例,如一般的应用系统往往有且仅有一个log文件操作类实例,或者,整个系统仅有一个等待事务队列等(注意:Singleton不是用来解决整个应用程序仅有一个实例这样的问题的),在这些情况下可以考虑使用Singleton模式。
Singleton(单件)模式用于保证一个类仅有一个实例,并提供一个访问该实例的全局访问点。(GoF : Ensure a class only has one instance , and provide a global point of access to it .)
二、结构
Singleton典型的结构如下图所示:
图 1:Singleton模式的类图示意
三、应用
Singleton模式从概念上讲应该是所有模式中最简单的,它的目的很简单:限制我们创建实例的个数,Only one is permited。不过,该模式也可以扩展到允许指定数量个实例的情况。
一眼看去,Singleton似乎有些像全局对象。但是实际上,并不能用全局对象代替Singleton模式,这是因为:其一,有些编程语言例如Java、C#等,根本就不支持全局变量。其二,全局对象的方法并不能阻止人们将一个类实例化多次:除了类的全局实例外,开发人员仍然可以通过类的构造函数创建类的多个局部实例。而Singleton模式则通过从根本上控制类的创建,将“保证只有一个实例”这个任务交给了类本身,开发人员不可能再有其它途径得到类的多个实例。这一点是全局对象方法与Singleton模式的根本区别。
四、优缺点
Singleton模式是作为 "全局变量"的替代品出现的,所以它具有全局变量的特点:全局可见;它也具有全局变量不具备的性质:同类型的对象实例只可能有一个。对于全局变量“贯穿应用程序的整个生命期”的特性,对于Singleton,视具体的实现方法,并不一定成立。
五、举例
实现该模式最简便的方法就是:采用 static变量,并将类的构造函数、拷贝构造函数、赋值函数声明为 private或者 protected,同时提供 public的实例创建 /访问函数。
示例代码如下:
#include <assert.h>
#include <assert.h>
#include <iostream>
using namespace std ;
class Singleton
{
public :
static Singleton * Instance () {
if (pInstance == NULL )
pInstance = new Singleton ();
return pInstance ;
}
protected :
Singleton () {}
private :
static Singleton * pInstance ;
Singleton ( const Singleton &) ;
Singleton & operator =( const Singleton &) ;
};
Singleton * Singleton ::pInstance = NULL ;
int main ()
{
//Singleton instance; // Error
//Singleton* pInstance = new Singleton; // Error
//Singleton instance = Singleton::Instance(); // Error
Singleton * pInstance1 = Singleton ::Instance (); // Good
Singleton * pInstance2 = Singleton ::Instance ();
cout .setf (ios ::hex , ios ::basefield );
cout << "pInstance1 = " << pInstance1 << endl ;
cout << "pInstance2 = " << pInstance2 << endl ;
cout .setf (ios ::dec , ios ::basefield );
assert (pInstance1 == pInstance2 );
return 0 ;
}
从结果我们可以我们两次获取到的是同一实例。上述实现方式只是众多实现方式中的一种,要将其改为多个实例的情况也十分简单。
但是,上述Singleton实现在多线程环境下不能正常工作,假定第一个线程完成:
if (pInstance == NULL )
检测后,CPU将控制权交给第二个(甚至是第三个、第四个 ...)线程,则最终我们可能获得n个而不是 1个实例。以下实现方式同样无法保证线程安全:
static Singleton * Instance () {
static Singleton _instance ;
return &_instance ;
}
下面的代码模拟了该问题(由于涉及多线程问题,为了简化问题,以下代码用Java编写):
class Singleton {
private static Singleton singleton = null ;
protected Singleton () {
try {
Thread .currentThread ().sleep ( 50 );
} catch (InterruptedException ex ) {
}
}
public static Singleton getInstance () {
if (singleton == null ) {
singleton = new Singleton ();
}
return singleton ;
}
}
public class SingletonTest {
private static Singleton singleton = null ;
public static void main (String [] args ) throws InterruptedException {
// Both threads call Singleton.getInstance().
Thread threadOne = new Thread ( new SingletonTestRunnable ()),
threadTwo = new Thread ( new SingletonTestRunnable ());
threadOne .start ();
threadTwo .start ();
threadOne .join ();
threadTwo .join ();
}
private static class SingletonTestRunnable implements Runnable {
public void run () {
// Get a reference to the singleton.
Singleton s = Singleton .getInstance ();
// Protect singleton member variable from multithreaded access.
synchronized (SingletonTest . class ) {
if (singleton == null ) // If local reference is null set it to the singleton
singleton = s ;
}
if (s == singleton )
System .out .println ( "Yes." );
else
System .out .println ( "No." );
}
}
}
要保证结果的正确性,必须对获取Singleton实例的方法进行同步控制,为此,有人提出了 double -checked locking的解决方案,相应的getInstance实现如下所示:
public static Singleton getInstance () {
if (singleton == null ) {
synchronized (Singleton . class ) {
if (singleton == null ) {
singleton = new Singleton ();
}
}
}
return singleton ;
}
但据说,由于Java中,在变量singleton完成初始化前,即
singleton = new Singleton ();
执行完毕前,sigleton的值可能是任意的,因此,若此时某个线程进入第一个 if判断,则可能判断结果为 false,从而返回一个无效的引用。因此,在Java中,唯一安全的方法是synchronized整个getInstance。
六、扩展
当系统中存在多个不同类型的Singleton时,可以考虑将Abstract Factory与Singleton模式结合,实现SingletonFactory,以下是一个SingletonFactory的例子:
import java .util .HashMap ;
public class SingletonFactory {
public static SingletonFactory singletonFactory = new SingletonFactory ();
private static HashMap map = new HashMap ();
protected SingletonFactory () {
// Exists only to defeat instantiation.
}
public static synchronized Singleton getInstance (String classname ) {
if (classname == null ) throw new IllegalArgumentException ( "Illegal classname" );
Singleton singleton = (Singleton )map .get (classname );
if (singleton != null ) {
return singleton ;
}
if (classname .equals ( "SingeltonSubclass1" ))
singleton = new SingletonSubclass1 (); // SingletonSubclass1 derives from Singleton class or implements Singleton interface
else if (classname .equals ( "SingeltonSubclass2" ))
singleton = new SingletonSubclass2 (); // SingletonSubclass2 derives from Singleton or implements Singleton interface
map .put (classname , singleton );
return singleton ;
}
}
最后,需要注意的是,在C ++中,单件对象的释放是个比较麻烦的事情,在上面的第一个示例中,由于没有释放单件对象,实际上是存在内存泄漏的,在释放单件对象的问题上,我们有以下几种选择:
1、在程序退出时由上层应用负责 delete,比如在MFC程序中,在ExitInstance中逐一检查SingletonFactory的静态map对象所保存的Singleton;
2、在Singleton内部采用静态auto_ptr来封装Singleton对象,按此方法修改后的示例 1如下所示:
#include <assert.h>
#include <iostream>
#include <memory>
using namespace std ;
class Singleton
{
public :
static Singleton * Instance () {
if (instance .get () == NULL )
{
instance .reset ( new Singleton ());
}
return instance .get ();
}
protected :
Singleton () {}
private :
static auto_ptr <Singleton > instance ;
Singleton ( const Singleton &) ;
Singleton & operator =( const Singleton &) ;
};
auto_ptr <Singleton > Singleton ::instance = auto_ptr <Singleton >(NULL );
int main ()
{
Singleton * pInstance1 = Singleton ::Instance (); // Good
Singleton * pInstance2 = Singleton ::Instance ();
cout .setf (ios ::hex , ios ::basefield );
cout << "pInstance1 = " << pInstance1 << endl ;
cout << "pInstance2 = " << pInstance2 << endl ;
cout .setf (ios ::dec , ios ::basefield );
assert (pInstance1 == pInstance2 );
return 0 ;
}
在很多情况下,我们的系统只允许某个类有一个或指定个数的实例,如一般的应用系统往往有且仅有一个log文件操作类实例,或者,整个系统仅有一个等待事务队列等(注意:Singleton不是用来解决整个应用程序仅有一个实例这样的问题的),在这些情况下可以考虑使用Singleton模式。
Singleton(单件)模式用于保证一个类仅有一个实例,并提供一个访问该实例的全局访问点。(GoF : Ensure a class only has one instance , and provide a global point of access to it .)
二、结构
Singleton典型的结构如下图所示:
图 1:Singleton模式的类图示意
三、应用
Singleton模式从概念上讲应该是所有模式中最简单的,它的目的很简单:限制我们创建实例的个数,Only one is permited。不过,该模式也可以扩展到允许指定数量个实例的情况。
一眼看去,Singleton似乎有些像全局对象。但是实际上,并不能用全局对象代替Singleton模式,这是因为:其一,有些编程语言例如Java、C#等,根本就不支持全局变量。其二,全局对象的方法并不能阻止人们将一个类实例化多次:除了类的全局实例外,开发人员仍然可以通过类的构造函数创建类的多个局部实例。而Singleton模式则通过从根本上控制类的创建,将“保证只有一个实例”这个任务交给了类本身,开发人员不可能再有其它途径得到类的多个实例。这一点是全局对象方法与Singleton模式的根本区别。
四、优缺点
Singleton模式是作为 "全局变量"的替代品出现的,所以它具有全局变量的特点:全局可见;它也具有全局变量不具备的性质:同类型的对象实例只可能有一个。对于全局变量“贯穿应用程序的整个生命期”的特性,对于Singleton,视具体的实现方法,并不一定成立。
五、举例
实现该模式最简便的方法就是:采用 static变量,并将类的构造函数、拷贝构造函数、赋值函数声明为 private或者 protected,同时提供 public的实例创建 /访问函数。
示例代码如下:
#include <assert.h>
#include <assert.h>
#include <iostream>
using namespace std ;
class Singleton
{
public :
static Singleton * Instance () {
if (pInstance == NULL )
pInstance = new Singleton ();
return pInstance ;
}
protected :
Singleton () {}
private :
static Singleton * pInstance ;
Singleton ( const Singleton &) ;
Singleton & operator =( const Singleton &) ;
};
Singleton * Singleton ::pInstance = NULL ;
int main ()
{
//Singleton instance; // Error
//Singleton* pInstance = new Singleton; // Error
//Singleton instance = Singleton::Instance(); // Error
Singleton * pInstance1 = Singleton ::Instance (); // Good
Singleton * pInstance2 = Singleton ::Instance ();
cout .setf (ios ::hex , ios ::basefield );
cout << "pInstance1 = " << pInstance1 << endl ;
cout << "pInstance2 = " << pInstance2 << endl ;
cout .setf (ios ::dec , ios ::basefield );
assert (pInstance1 == pInstance2 );
return 0 ;
}
从结果我们可以我们两次获取到的是同一实例。上述实现方式只是众多实现方式中的一种,要将其改为多个实例的情况也十分简单。
但是,上述Singleton实现在多线程环境下不能正常工作,假定第一个线程完成:
if (pInstance == NULL )
检测后,CPU将控制权交给第二个(甚至是第三个、第四个 ...)线程,则最终我们可能获得n个而不是 1个实例。以下实现方式同样无法保证线程安全:
static Singleton * Instance () {
static Singleton _instance ;
return &_instance ;
}
下面的代码模拟了该问题(由于涉及多线程问题,为了简化问题,以下代码用Java编写):
class Singleton {
private static Singleton singleton = null ;
protected Singleton () {
try {
Thread .currentThread ().sleep ( 50 );
} catch (InterruptedException ex ) {
}
}
public static Singleton getInstance () {
if (singleton == null ) {
singleton = new Singleton ();
}
return singleton ;
}
}
public class SingletonTest {
private static Singleton singleton = null ;
public static void main (String [] args ) throws InterruptedException {
// Both threads call Singleton.getInstance().
Thread threadOne = new Thread ( new SingletonTestRunnable ()),
threadTwo = new Thread ( new SingletonTestRunnable ());
threadOne .start ();
threadTwo .start ();
threadOne .join ();
threadTwo .join ();
}
private static class SingletonTestRunnable implements Runnable {
public void run () {
// Get a reference to the singleton.
Singleton s = Singleton .getInstance ();
// Protect singleton member variable from multithreaded access.
synchronized (SingletonTest . class ) {
if (singleton == null ) // If local reference is null set it to the singleton
singleton = s ;
}
if (s == singleton )
System .out .println ( "Yes." );
else
System .out .println ( "No." );
}
}
}
要保证结果的正确性,必须对获取Singleton实例的方法进行同步控制,为此,有人提出了 double -checked locking的解决方案,相应的getInstance实现如下所示:
public static Singleton getInstance () {
if (singleton == null ) {
synchronized (Singleton . class ) {
if (singleton == null ) {
singleton = new Singleton ();
}
}
}
return singleton ;
}
但据说,由于Java中,在变量singleton完成初始化前,即
singleton = new Singleton ();
执行完毕前,sigleton的值可能是任意的,因此,若此时某个线程进入第一个 if判断,则可能判断结果为 false,从而返回一个无效的引用。因此,在Java中,唯一安全的方法是synchronized整个getInstance。
六、扩展
当系统中存在多个不同类型的Singleton时,可以考虑将Abstract Factory与Singleton模式结合,实现SingletonFactory,以下是一个SingletonFactory的例子:
import java .util .HashMap ;
public class SingletonFactory {
public static SingletonFactory singletonFactory = new SingletonFactory ();
private static HashMap map = new HashMap ();
protected SingletonFactory () {
// Exists only to defeat instantiation.
}
public static synchronized Singleton getInstance (String classname ) {
if (classname == null ) throw new IllegalArgumentException ( "Illegal classname" );
Singleton singleton = (Singleton )map .get (classname );
if (singleton != null ) {
return singleton ;
}
if (classname .equals ( "SingeltonSubclass1" ))
singleton = new SingletonSubclass1 (); // SingletonSubclass1 derives from Singleton class or implements Singleton interface
else if (classname .equals ( "SingeltonSubclass2" ))
singleton = new SingletonSubclass2 (); // SingletonSubclass2 derives from Singleton or implements Singleton interface
map .put (classname , singleton );
return singleton ;
}
}
最后,需要注意的是,在C ++中,单件对象的释放是个比较麻烦的事情,在上面的第一个示例中,由于没有释放单件对象,实际上是存在内存泄漏的,在释放单件对象的问题上,我们有以下几种选择:
1、在程序退出时由上层应用负责 delete,比如在MFC程序中,在ExitInstance中逐一检查SingletonFactory的静态map对象所保存的Singleton;
2、在Singleton内部采用静态auto_ptr来封装Singleton对象,按此方法修改后的示例 1如下所示:
#include <assert.h>
#include <iostream>
#include <memory>
using namespace std ;
class Singleton
{
public :
static Singleton * Instance () {
if (instance .get () == NULL )
{
instance .reset ( new Singleton ());
}
return instance .get ();
}
protected :
Singleton () {}
private :
static auto_ptr <Singleton > instance ;
Singleton ( const Singleton &) ;
Singleton & operator =( const Singleton &) ;
};
auto_ptr <Singleton > Singleton ::instance = auto_ptr <Singleton >(NULL );
int main ()
{
Singleton * pInstance1 = Singleton ::Instance (); // Good
Singleton * pInstance2 = Singleton ::Instance ();
cout .setf (ios ::hex , ios ::basefield );
cout << "pInstance1 = " << pInstance1 << endl ;
cout << "pInstance2 = " << pInstance2 << endl ;
cout .setf (ios ::dec , ios ::basefield );
assert (pInstance1 == pInstance2 );
return 0 ;
}