Head first设计模式学习第五天
单例模式:确保一个类只有一个实例,并提供一个全局访问点。
单例模式的好处,可以确保程序中使用的全局资源只有一份。一般用于像线程池或数据库连接上,可以减少因为过多的实例化同一个对象而造成代码运行时的bug。单例模式中构造方法是私有的,但会放出一个公有的静态方法,当调用该静态方法的时候就会调用这个实例,但这个实例有可能是这次调用时创建的,也有可能是以前创建的。代码展示:
publicclass ChocolateBoiler { privatestaticbooleanempty; privatestaticbooleanboiled; privatestatic ChocolateBoiler uniqueInstance;
private ChocolateBoiler(){ empty=true; boiled=false; } publicstatic ChocolateBoiler getInstance(){ if(uniqueInstance==null){ uniqueInstance=new ChocolateBoiler(); } returnuniqueInstance; } publicvoid fill() { if (isEmpty()) { empty = false; boiled = false; } }
publicvoid drain() { if (!isEmpty() && isBoiled()) { // drain the boiled milk and chocolate empty = true; } }
publicvoid boil() { if (!isEmpty() && !isBoiled()) { // bring the contents to a boil boiled = true; } }
publicboolean isEmpty() { returnempty; }
publicboolean isBoiled() { returnboiled; } }
|
直白的讲,把某个类设计成自己管理的一个单独实例,可以同时避免其他类再自行产生实例。要想取得单件实例,只能通过单件类。同时提供这个实例的全局访问点,需要实例时,向类查询,就会返回实例。但上面的例子是利用延迟实例化的方式创建单件,这种做法对资源敏感的对象特别重要。但这样写的话,进行多线程操作时会出现没进行锁操作的一切问题。而只要把getInstance()变成同步(synchronized)方法,问题就解决了,这样就能确保不会有两个及以上的线程进入方法。
但使用同步锁的做法会极大的影响程序的执行效率,甚至有可能拖垮性能,所以一般使用单件模式有三种选择:
1:如果全局访问点也就是上面代码的getInstance()的性能对应用程序不是很关键,就什么都别做。因为同步getInstance()的方法即简单又有效,但如果getInstance()的程序使用在频繁运行的地方时,就可以选择第三种方法,这种模式也就是俗称的懒汉模式;
public class Singleton { private static Singleton uniqueInstance; private Singleton() {} public static synchronized Singleton getInstance() { if (uniqueInstance == null) { uniqueInstance = new Singleton(); } return uniqueInstance; } } |
2:使用“急切”创建实例,而不用延迟实例化的做法。如果应用程序总是创建并使用单件实例,或者在创建和运行时方面的负担不太繁重,可以使用下面的方法:
publicclass Singleton { privatestatic Singleton uniqueInstance=new Singleton(); private Singleton(){} publicstatic Singleton getInstance(){ returnuniqueInstance; } }
|
在静态初始化器中创建单件,这保证了线程的安全。利用这个做法,依赖JVM在加载这个类时马上创建唯一的单件实例。JVM保证在任何线程访问uniqueInstance静态变量之前,一定先创建此实例。但这会给运行内存制造压力,每次调用Singleton时都会先创建uniqueInstance,会造成内存的浪费。而这种做法一般被我们称做“饿汉模式”。
publicclass Singleton { privatevolatilestatic Singleton uniqueInstance; private Singleton(){} publicstatic Singleton getInstance(){ if(uniqueInstance==null){ synchronized (Singleton.class) { if(uniqueInstance==null){ uniqueInstance=new Singleton(); } } } returnuniqueInstance; } } |
这样的做法,可以大大减少getInstance()的时间耗费。而这种做法也就是俗称的“饿汉模式懒加载”。
注意:双重检查加锁不适用于JDK1.4及更早版本的Java。因为volatile关键字的实现会导致双重检查加锁的失效。所以使用JDK1.4及更早版本的Java,就请不要利用此技巧实现单例模式。
注意:如果使用多个加载器,可能导致单件失效而产生多个实例。因为不同的类加载器可能加载同一个类,从整个程序看,同一个类会被加载多次。如果这样的事发生在单例上,就会产生多个单件并存的现象。所以当程序有多个类加载器又同时使用单件模式时,最好自行指定加载器,并指定同一个类加载器。
注意:使用JVM1.2或之前的版本,必须建立单件注册表,以免垃圾收集器将单件回收(这是一个bug,JVM1.3及以后的版本已经修正了)。