singleton

来源:互联网 发布:华彩软件破解网 编辑:程序博客网 时间:2024/05/01 13:17
定义

名称:Singleton

结构:



意图:
保证一个类仅有一个实例,并提供一个访问它的全局访问点。

适用性:
  • 当类只能有一个实例而且客户可以从一个众所周知的访问点访问它时。
  • 当这个唯一实例应该是通过子类化可扩展的,并且客户应该无需更改代码就能使用一个扩展的实例时。


经典案例

1、java.lang.Runtime 以及资源管理
      在Java 语言内部,java.lang.Runtime 对象就是一个使用单例模式的例子。在每一个Java应用程序里面,都有惟一的一个Runtime 对象。通过这个Runtime 对象,应用程序可以与其运行环境发生相互作用。
      一些资源管理器常常设计成单例模式。
      在计算机系统中,需要管理的资源包括软件外部资源,譬如每台计算机可以有若干个打印机,但只能有一个Printer Spooler,以避免两个打印作业同时输出到打印机中。每台计算机可以有若干传真卡,但是只应该有一个软件负责管理传真卡,以避免出现两份传真作业同时传到传真卡中的情况。每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。
      需要管理的资源包括软件内部资源,譬如,大多数的软件都有一个(甚至多个)属性(properties)文件存放系统配置。这样的系统应当由一个对象来管理一个属性文件。
      需要管理的软件内部资源也包括譬如负责记录网站来访人数的部件,记录软件系统内部事件、出错信息的部件,或是对系统的表现进行检查的部件等。这些部件都必须集中管理,不可政出多头。
      这些资源管理器构件必须只有一个实例,这是其一;它们必须自行初始化,这是其二;允许整个系统访问自己这是其三。因此,它们都满足单例模式的条件,是单例模式的应用。

最佳实践

1、饿汉式、懒汉式和登记式单例
      在google上搜索eager singleton或lazy singleton,最前面的几条纪录居然都是中文网页(难道中国人特别关注设计模式?)。这是三种初始化 singleton的方式,饿汉式在类被加载时就被实例化。懒汉式类加载时,不被实例化,在第一次引用时实例化。 由于饿汉式、懒汉式都不能被继承,扩展的登记式singleton可以被继承。
      注意到lazy initialization形式中的synchronized,这个synchronized很重要,如果没有synchronized,那么使用getInstance()是有可能得到多个Singleton实例。

a. 饿汉式singleton
public class EagerSingleton
{
private static final EagerSingleton m_instance = new Eagersingleton();
private Eagersingleton(){} //由于构造函数是私有的,因此,此类不能被继承。

public static EagerSingleton getInstance()
{
return m_instance;
}

}

b. 懒汉式singleton
public class LazySingleton
{
private static LazySingleton m_instance = null;
private LazySingleton(){};
synchronized public static LazySingleton getInstance()
{
if( m_instance == null )
{
m_instance = new LazySingleton();
}
return m_instance;
}
}

c. 登记式singleton
import java.util.HashMap;
public class RegSingleton
{
static private HashMap m_registry = new HashMap();
static
{
RegSingleton x = new regSingleton();
m_registry.put(x.getClass().getName(), x);
}
protect RegSingleton(){}
static public RegSingleton getInstance(String name)
{
if(name == null )
{
name = "RegSingleton";
}
if(m_registry.get(name ) == null )
{
m_registry.put(name, Class.forName(name).newInstance();
}
catch(Exception e)
{
System.out.println("Error happened.");
}
return (RegSingleton)(m_registry.get(name));
}
}

2、单例的线程安全问题 
      对singleton的线程安全的讨论主要源自于多线程环境下懒汉式singleton获得实例时有可能会有多个线程进入导致判断为null时发生混乱而实际生成多个实例。如下:
class Foo
{
  private Helper helper = null;
  public Helper getHelper()
  {
    if (helper == null)
    {
      helper = new Helper();
    }
    return helper;
  }
// other functions and members...
}
如果有两个线程A 和B 几乎同时到达 if (helper == null)语句的外面的话,假设线程A 比线程B 早一点点,那么:
(1)A 会首先进入if (helper == null)块的内部,并开始执行new Helper()语句。此时,helper 变量仍然是null,直到线程A 的new Helper()语句返回并给helper 变量赋值为止。
(2)但是,线程B 并不会在if (helper == null)语句的外面等待,因为此时helper == null是成立的,它会马上进入if (helper == null)语句块的内部。这样,线程B 会不可避免地执行helper = new Helper();语句,从而创建出第二个实例来。
(3)线程A 的helper = new Helper();语句执行完毕后,helper 变量得到了真实的对象引用, (helper == null)不再为真。第三个线程不会再进入if (helper == null)语句块的内部了。
(4)线程B 的helper = new Helper();语句也执行完毕后,helper 变量的值被覆盖。但是第一个Helper 对象被线程A 引用的事实不会改变。
这时,线程A 和B 各自拥有一个独立的Helper 对象,而这是错误的。
      
有人会使用这样的方法来避免异步的产生:
// Broken multithreaded version
// "Double-Checked Locking" idiom
class Foo
{
  private Helper helper = null;
  public Helper getHelper()
  {
    if (helper == null) //第一次检查(位置1)
    {
      //这里会有多于一个的线程同时到达 (位置2)
      synchronized(this)
      {
        //这里在每个时刻只能有一个线程 (位置3)
        if (helper == null) //第二次检查 (位置4)
        {
          helper = new Helper();
        }
      }
    }
    return helper;
  }
  // other functions and members...
}
这里假设线程A 和B 作为第一批调用者同时或几乎同时调用静态工厂方法。
(1)因为线程A 和B 是第一批调用者,因此,当它们进入此静态工厂方法时,helper变量是null。因此,线程A 和B 会同时或几乎同时到达位置1。
(2)假设线程A 会首先到达位置2,并进入synchronized(this)到达位置3。这时,由于synchronized(this) 的同步化限制,线程B 无法到达位置3,而只能在位置2 等候。
(3)线程A 执行helper = new Helper()语句,使得helper 变量得到一个值,即对一个Helper 对象的引用。此时,线程B 只能继续在位置2 等候。
(4)线程A 退出synchronized(this),返回Helper 对象,退出静态工厂方法。
(5)线程B 进入synchronized(this)块,达到位置3,进而达到位置4。由于helper 变量已经不是null 了,因此线程B 退出synchronized(this),返回helper 所引用的Helper 对象(也就是线程A 所创建的Helper 对象),退出静态工厂方法。
到此为止,线程A 和线程B 得到了同一个Helper 对象。可以看到,在上面的方法getInstance()中,同步化仅用来避免多个线程同时初始化这个类,而不是同时调用这个静态工厂方法。
问题在于双重检查成例对Java 语言编译器不成立,具体请参考http://www.javaresearch.org/members/jeffyan77/javapatterns/chp15.pdf

所以要想避免线程安全问题,最终版本应该对getInstance()方法做同步或者使用饿汉式单例


3、避免使用singleton的情况
      有时在某些情况下,使用Singleton并不能达到Singleton的目的,如有多个Singleton对象同时被不同的类装入器装载;在EJB这样的分布式系统中使用也要注意这种情况,因为EJB是跨服务器,跨JVM的。

      我们以SUN公司的宠物店源码(Pet Store 1.3.1)的ServiceLocator为例稍微分析一下:
在Pet Store中ServiceLocator有两种,一个是EJB目录下;一个是WEB目录下,我们检查这两个ServiceLocator会发现内容差不多,都是提供EJB的查询定位服务,可是为什么要分开呢?仔细研究对这两种ServiceLocator才发现区别:在WEB中的ServiceLocator的采取Singleton模式,ServiceLocator属于资源定位,理所当然应该使用Singleton模式。但是在EJB中,Singleton模式已经失去作用,所以ServiceLocator才分成两种,一种面向WEB服务的,一种是面向EJB服务的。

      Singleton模式看起来简单,使用方法也很方便,但是真正用好,是非常不容易,需要对Java的类 线程 内存等概念有相当的了解。
      总之:如果你的应用基于容器,那么Singleton模式少用或者不用,可以使用相关替代技术。


相关模式
1、abstract factory / builder
      有一些模式可以使用单例模式,如抽象工厂模式可以使用单例模式,将具体工厂类设计成单例类;建造模式可以使用单例模式,将具体建造类设计成单例类。


参考文章
1、Singleton 模式 在 Java 中的应用(主要看下面的讨论)
http://www.douzhe.com/docs/bbsjh/14/469.html
2、一个Singleton模式的问题
http://www.shecn.com/best/g30/u1143299.htm
3、Double-checked locking and the Singleton pattern
http://www-106.ibm.com/developerworks/java/library/j-dcl.html?dwzone=java

原创粉丝点击