设计模式记录(2.2)
第十五章 单例模式
单例模式的要点
显然单例模式的要点有三个:一个某个类只能有一个实例;二是它必须自行创建这个事例;三是它必须自行向整个系统提供这个事例。
单例模式的结构
单例类只能有一个实例。
单例类必须自己创建自己的唯一的实例。
单例类必须给所有其他对象提供这一实例。
由于java语言的特点,使得单例模式在java语言的实现上有自己的特点。这些特点主要表现在单例类如何将自己实例化上。
饿汉式单例类
饿汉式单例类是在java语言里实现起来最为简便的单例类,实现代码如下:
package com.javapatterns.singleton.demos;public class EagerSingleton { private static final EagerSingleton m_instance = new EagerSingleton(); private EagerSingleton() { } public static EagerSingleton getInstance() { return m_instance; } }
可以看出,在这个类被夹在时,静态变量m_instance会被初始化,此时类的私有构造子会被调用。这时候,单例类的唯一实例就被创建出来了。
java语言中单例类的一个最重要的特点是类的构造子是私有的,从而避免外界利用构造子直接创建出任意多的实例。值得指出的是,由于构造子是私有的,因此此类不能被继承。
懒汉式单例类
与饿汉式单例类相同指出是,类的构造子是私有的。与饿汉式单例类不同的是,懒汉式单例类在第一次被引用时将自己实例化。如果加载器是静态的,那么在懒汉式单例类被加载时不会讲自己实例化。
懒汉式单例类的源代码如下:
package com.javapatterns.singleton.demos;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;} }
饿汉式和懒汉式的对比
饿汉式单例类在自己被加载时就将自己实例化。即便加载器是静态的,在饿汉式单例类被加载时仍会讲自己实例化。单从资源利用效率角度来讲,这个比懒汉式单例类稍差些。从速度和反应时间角度来讲,则比懒汉式单例类稍好些。然而,懒汉式单例类在实例化时,必须处理好在多个线程同时首次引用此类时的访问限制问题,特别是当单例类作为资源控制器在实例化时必然涉及资源初始化,而资源初始化很有可能消耗时间。这意味着出现多线程同时首次引用此类的几率变得较大。
实际上,java与模式这本书认为饿汉式单例类更符合java语言本身的特点。
登记式单例类
代码如下:
package com.javapatterns.singleton.demos;import java.util.HashMap;public class RegSingleton { protected RegSingleton() {} static public RegSingleton getInstance(String name) { if (name == null) { name = "com.javapatterns.singleton.demos.RegSingleton"; } System.out.println("From RegSingleton: requesting for " + name ); if (m_registry.get(name) == null) { try { m_registry.put( name, Class.forName(name).newInstance() ) ; } catch(ClassNotFoundException e) { System.out.println("Class " + name + " is not found."); } catch(InstantiationException e) { System.out.println("Class " + name + " can not be instantiated."); } catch(IllegalAccessException e) { System.out.println("Class " + name + " can not be accessed."); } } return (RegSingleton) (m_registry.get(name) ); } static private HashMap m_registry = new HashMap(); static { RegSingleton x = new RegSingleton(); m_registry.put( x.getClass().getName() , x); } public String about() { return "Hello, I am RegSingleton."; }}
上面类的子类RegSingletonChild 需要父类的帮组才能实例化。
package com.javapatterns.singleton.demos;import java.util.HashMap;public class RegSingletonChild extends RegSingleton{ public RegSingletonChild() {} static public RegSingletonChild getInstance() { return (RegSingletonChild) RegSingleton.getInstance( "com.javapatterns.singleton.demos.RegSingletonChild" ); } public String about() { return "Hello, I am RegSingletonChild."; }}
package com.javapatterns.singleton.demos;public class RegSingletonTest{ public static void main(String[] args) { System.out.println( RegSingleton.getInstance("com.javapatterns.singleton.demos.RegSingleton").about() ) ; System.out.println( RegSingleton.getInstance(null).about() ) ; System.out.println( RegSingleton.getInstance("com.javapatterns.singleton.demos.RegSingletonChild").about() ) ; System.out.println( RegSingletonChild.getInstance().about()) ; }}
结果是:
From RegSingleton: requesting for com.javapatterns.singleton.demos.RegSingletonHello, I am RegSingleton.From RegSingleton: requesting for com.javapatterns.singleton.demos.RegSingletonHello, I am RegSingleton.From RegSingleton: requesting for com.javapatterns.singleton.demos.RegSingletonChildHello, I am RegSingletonChild.From RegSingleton: requesting for com.javapatterns.singleton.demos.RegSingletonChildHello, I am RegSingletonChild.
在GoF原始的例子中并没有getInstance()方法,这一得到子类必须调用的getInstance(String name)方法并传入子类的名字,因此很不方便。本章在登记式单例类子类的例子里,加入了getInstance()方法,这一座的好处是RegSingletonChild可以通过这个方法返还自己的实例。而这一座的缺点是,由于数据类型不同,无法再RegSingleton提供这样一个方法。
由于子类必须允许父类以构造子调用生产例子,因此,它的构造子必须是公开的。这样一来,就等于允许了以这样方式生产实例而不再父类的登记中。这是登记式单例类的一个缺点。
GoF曾指出,由于父类的实例必须存在才能有子类的实例,这在有些情况下是一个浪费。这是登记式单例类的另一个缺点。
使用单例模式的条件
使用单例模式有一个必要条件:在一个系统要求一个类只有一个实例时才应当使用单例模式。
多个JVM系统的分散式系统
EJB容器有能力将一个EJB的实例跨国几个JVM调用。由于单例对象不是EJB因此,单例类局限于某一个JVM中。换言之,如果EJB在跨国JVM后仍然需要引用同一个单例类的话,这个单例类就会在数个JVM中被实例化,造成多个单例对象的实例出现。一个J2EE应用系统可能分布在数个JVM中,这时候不一定需要EJB就能造成多个单例类的实例出现在不同JVM中的情况。
如果这个单例类是没有状态的,那么就没有问题。但是如果这个单例类是有状态的,那么问题就来了。举例来说,如果一个单例对象可以持有一个int类型的属性没用来给一个系统提供一个数值唯一的序列号码没座位某个贩卖系统的账号号码的话,用户会看到同一个号码出现好几次。
在任何使用了EJB RMI和JINI技术的分散式系统中,应当避免使用有状态的单例模式。
java语言中的单例模式
java的Runtime对象
在java语言内部mjava.lang.Runtime对象就是一个使用单例模式的例子。在每一个java应用程序里面,都有唯一的一个Runtime对象。通过这个Runtime对象,应用程序可以与其运行环境发生相互作用。
代码如下:
import java.io.*;public class CmdTest {public static void main(String[] args) {Process proc = Runtime.getRuntime().exec("notepad.exe");}}
不完全的单例类
程序如下:
package com.javapatterns.singleton.demos;public class LazySingleton{ public LazySingleton() { } synchronized public static LazySingleton getInstance() { if (m_instance == null) { m_instance = new LazySingleton(); } return m_instance;} private static LazySingleton m_instance = null;}
上面的代码看起来是一个懒汉式单例类,但是它有一个公开的构造子。由于外界可以使用构造子创建出任意多个此类的实例,这违背了单例类只能有一个实例的特征,因此这个类是不完全的单例类。
造成这种情况出现的原因有以下几种可能:
1.初学者的错误。
2.当初出于考虑不周,讲一个类设计成为单例类,后来发现此类应当有多于一个实例。为了弥补错误,干脆将构造子改为公开的,以便在需要多于一个的实例时,可以随时调用构造子创建实例。
3.设计师的java知识很好,知道单例如何用,但是还是这样,因为他意在使用一个改良的单例模式。这时候,除去共有的构造子不符合单例模式的要求之外,这个类必须是很好的单例模式。
默认实例模式
有些设计师将这种不完全的单例模式叫做“默认实例模式”。这样做的唯一好处是。这种模式允许客户端选择如何将类实例化:创建新的自己独有的实例,或者使用共享的实例。
java与模式这本书建议读者不要这样做。。。。
双重检查成立的研究
成例是一种代码层次上的模式,有很多人认为双重检查成例可以使用在懒汉单例模式里面。
线程安全的版本
class Foo {private Helper helper = null;public synchronized Helper getHelper() {if(helper == null) {helper = new Heper();return helper;}}}
画蛇添足的双重检查
使用双重检查成例的懒汉式单例模式代码:
class Foo {private Helper helper = null;public Helper getHelper() {if(helper == null) { //第一次检查//这里会有多于一个的线程同时到达synchronized (this) {//这里在每个时刻只能有一个线程if(helper == null) { //第二次检查helper = new Helper();}}}return helper;}}
在java编译器中,Foo类的初始化与helper变量赋值的顺序不可预料。如果一个线程在没有同步化的条件下读取helper引用,并调用这个对象的方法的话,可能会发现对象的初始化过程尚未完成,从而造成崩溃。
文献[BLOCH01]指出:一般而言,双重检查成例对java语言来说是不成立的。
在一般情况下,使用饿汉式单例模式或者对整个静态工厂方法同步化的懒汉式单例模式足以解决在实际设计工作中遇到的问题。
第十七章 多例模式
作为对象的创建模式,多例模式中的多例类可以有多个实例,而且多例类必须自己创建、管理自己的实例,并向外界提供自己的实例。
有上限的多例类
package com.javapatterns.multilingual.dice;import java.util.Random;import java.util.Date;public class Die{ private static Die die1 = new Die(); private static Die die2 = new Die(); private Die() { } public static Die getInstance(int whichOne) { if (whichOne == 1) { return die1; } else { return die2; } } public synchronized int dice() {Date d = new Date();Random r = new Random( d.getTime() );int value = r.nextInt();value = Math.abs(value);value = value % 6;value += 1;System.out.println(value); return value; }}
在多例类Die中,使用了饿汉方式创建了两个Die的实例。根据静态工厂方法的参数,工厂方法返还两个实例中的一个。Die对象的die方法代表掷骰子,这个方法会返还一个1到6之间的随机数,测试代码如下:
package com.javapatterns.multilingual.dice;public class Client{ private static Die die1;private static Die die2; public static void main(String[] args) {die1 = Die.getInstance(1); die2 = Die.getInstance(2); die1.dice(); die2.dice(); }}
无上限多例模式
多例类的实例数目并不需要有上限,实例数目没有上限的多例模式就叫做无上限多例模式,可以用一个容器来储存。
自我理解多例模式就是单例的推广,自不过数目不同而已。