【读书笔记】HeadFirst设计模式——单件不简单:详述实现Singleton模式需要考虑的方方面面

来源:互联网 发布:备案域名和备案空间 编辑:程序博客网 时间:2024/06/17 21:45

(参考:

深入浅出单实例Singleton设计模式

)

什么是单件?

单件就是保证一个类仅有一个实例,并提供一个访问它的全局访问点。——GOF

单件模式简单吗?

简单,的确简单,因为只有一个类。

单件不简单!

其实单件并不见得简单,而且还有点小复杂。其复杂度正是为了保证单件所要达到“仅有一个实例”的宏伟目标而引起的。

当然在一般情况下,单件是简单的。但是在考虑了懒加载、并发、反射、序列化、子类化等诸多因素后,为了保证只有一个实例,复杂度就大大提高了。

下面就从这些方面一一来看如何保证单件只有一个实例,然后看看是不是并不像想象的简单。


1.So easy!饿汉

很简单,直接访问静态域,为防修改,定义成final的。当然构造函数必须是私有的。

/** * 最简单的单件实现,直接访问静态域 *  * @author nathan *  */public class SimpleSingleton {public final static SimpleSingleton INSTANCE = new SimpleSingleton();private SimpleSingleton() {}public void doSomething() {System.out.println("SimpleSingleton.doSomething");}}

2.1的变体

通过静态方法访问

/** * 最简单的单件实现,访问静态方法 *  * @author nathan *  */public class SimpleSingleton2 {private final static SimpleSingleton2 INSTANCE = new SimpleSingleton2();private SimpleSingleton2() {}public void doSomething() {System.out.println("SimpleSingleton2.doSomething");}public static SimpleSingleton2 getInstance() {return INSTANCE;}}

3.复杂度+1:反反射调用私有构造函数创建实例。

实现方式,在构造函数中判断是否为空,否则抛出异常

/** * 反反射调用构造函数的单件实现 *  * @author nathan *  */public class AntiRefSingleton {public static AntiRefSingleton instance = new AntiRefSingleton();private AntiRefSingleton() {if (instance != null) {throw new RuntimeException("This is a Singleton Class, please use AntiRefSingleton.instance to get the Object!");}}public void doSomething() {System.out.println("SimpleSingleton.doSomething");}}


3.复杂度+2:懒汉

为防止加载没用的加载比较耗时的单件

/** * 懒加载的单件实现,但有并发问题 *  * @author nathan *  */public class LazySingleton {private static LazySingleton instance = null;private LazySingleton() {if (instance != null) {throw new RuntimeException("This is a Singleton Class, please use the getInstance function to get the Object!");}}public void doSomething() {System.out.println("LazySingleton.doSomething");}public static LazySingleton getInstance() {if (instance == null) {instance = new LazySingleton();}return instance;}}

4.复杂度+3:并发控制

懒汉做事总是不靠谱,必须要有额外的机制保证线程安全——DCL(双重检查加锁)

/** * 使用DCL技术实现的并发安全的懒加载单件实现 *  * @author nathan *  */public class ConcurrentSingleton {/** * 必须声明为volatile的 */private static volatile ConcurrentSingleton instance = null;private ConcurrentSingleton() {if (instance != null) {throw new RuntimeException("This is a Singleton Class, please use the getInstance function to get the Object!");}}public void doSomething() {System.out.println("ConcurrentSingleton.doSomething");}public static ConcurrentSingleton getInstance() {if (instance == null) {// 使用双重检查锁定技术synchronized (ConcurrentSingleton.class) {if (instance == null) {instance = new ConcurrentSingleton();}}}return instance;}}

5.懒汉变体

使用静态内部类,也是线程安全的

/** * 使用静态内部类实现懒汉单例,而且是线程安全的 *  * @author nathan *  */public class HolderSingleton {private static class SingletonHolder {private static final HolderSingleton INSTANCE = new HolderSingleton();}public static HolderSingleton getInstance() {return SingletonHolder.INSTANCE;}}

6.复杂度+4:接下来考虑序列化

《Effective Java》中作者已经给出了方案,即添加readResolve方法。如下:

/** * 可序列化的单件实现(同时是并发安全的懒加载的),但只能在同一个jvm中使用,不能跨jvm *  * @author nathan *  */public class SerializableSingleton implements Serializable {private static final long serialVersionUID = 5691590550973506283L;private transient String description;public void doSomething() {description = "SerializableSingleton";}@Overridepublic String toString() {return super.toString() + " [description=" + description + "]";}/** * 必须声明为volatile的 */private static volatile transient SerializableSingleton instance = null;private SerializableSingleton() {if (instance != null) {throw new RuntimeException("This is a Singleton Class, please use the getInstance function to get the Object!");}}public static SerializableSingleton getInstance() {if (instance == null) {// 使用双重检查锁定技术synchronized (SerializableSingleton.class) {if (instance == null) {instance = new SerializableSingleton();}}}return instance;}private Object readResolve() {// 抛弃反序列化的实例,返回原实例return instance;}}

7.复杂度+5:跨jvm序列化

第6中方案作者给出了解决问题的思路,但未真正解决序列化问题。因为它只能在同一个jvm中适应。但是在同一个jvm中序列化单例似乎意义不大。下面是kuajvm的单例实现方式。简单修改6中的readResolve方法即可。

/** * 能跨jvm使用的可序列化的单件实现(同时是并发安全、懒加载的) *  * @author nathan *  */public class SerializableSingleton2 implements Serializable {private static final long serialVersionUID = 5691590550973506283L;private String description;private int count;public void doSomething() {description = "SerializableSingleton2";count = 100;}@Overridepublic String toString() {return super.toString() + " [description=" + description + ",count="+ count + "]";}public void setCount(int count) {this.count = count;}public int getCount() {return count;}/** * 必须声明为volatile的 */private static volatile SerializableSingleton2 instance = null;private SerializableSingleton2() {if (instance != null) {throw new RuntimeException("This is a Singleton Class, please use the getInstance function to get the Object!");}}public static SerializableSingleton2 getInstance() {if (instance == null) {// 使用双重检查锁定技术synchronized (SerializableSingleton2.class) {if (instance == null) {instance = new SerializableSingleton2();}}}return instance;}private Object readResolve() {if (instance == null) {// 使用双重检查锁定技术synchronized (SerializableSingleton2.class) {if (instance == null) {instance = this;// 如果是第一次反序列化,则使用该实例,否则不管它}}}return instance;}public static void serialize(String file) throws Exception {ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File(file)));oos.writeObject(instance);}public static void antiSerialize(String file) throws Exception {ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File(file)));instance = (SerializableSingleton2) ois.readObject();}}

8.复杂度+6:考虑单件的继承

在GOF的《设计模式》中给出了方案,即采用register的方式。但按其书中描述,子类的构造函数必须是公有的,这就违背了单例的初衷。因此必须借助“反射”机制实现对子类的实例化。

9.复杂度+7:再考虑基于继承的单件体系的跨jvm的序列化

(8和9合后的并代码如下)

/** * 一个可子类化、可序列化的单件实现 *  * @author nathan *  */public class SubableSingleton implements Serializable {private String description;private int count;public void doSomething() {description = "SubableSingleton";count = 100;}@Overridepublic String toString() {return super.toString() + " [description=" + description + ",count="+ count + "]";}public void setCount(int count) {this.count = count;}public int getCount() {return count;}// 以下代码实现单件支持private static final long serialVersionUID = 5713856529741473199L;private static SingletonHolder holder = null;private String name;protected SubableSingleton() {this(SubableSingleton.class);}protected SubableSingleton(Class<? extends SubableSingleton> clazz) {if (holder.lookup(clazz.getName()) != null) {throw new RuntimeException("This is a Singleton Class, please use getInstance function to get the Object!");}name = clazz.getName();}/** * 注意:使用synchronized代替DCL进行简单并发控制 *  * @param clazz * @return */public static synchronized SubableSingleton getInstance(Class<? extends SubableSingleton> clazz) {if (holder == null) {holder = new SingletonHolder();}SubableSingleton instance = holder.lookup(clazz.getName());if (instance == null) {try {Constructor<? extends SubableSingleton> constructor = clazz.getDeclaredConstructor();constructor.setAccessible(true);instance = constructor.newInstance();holder.register(clazz.getName(), instance);} catch (Exception e) {e.printStackTrace();}}return instance;}private synchronized Object readResolve() {System.out.println("SubableSingleton.readResolve");if (holder == null) {holder = new SingletonHolder();holder.register(this.name, this);}return holder.lookup(this.name);}public static void serialize(String file) throws Exception {ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File(file)));oos.writeObject(holder);}public static void antiSerialize(String file) throws Exception {ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File(file)));holder = (SingletonHolder) ois.readObject();}/** * 单例持有类,私有 *  * @author nathan *  */private static class SingletonHolder implements Serializable {private static final long serialVersionUID = -4221190210772287103L;private Map<String, SubableSingleton> registry = new HashMap<String, SubableSingleton>();public void register(String name, SubableSingleton subableSingleton) {registry.put(name, subableSingleton);}public SubableSingleton lookup(String name) {return registry.get(name);}private synchronized Object readResolve() {System.out.println("SingletonHolder.readResolve");// 抛弃反序列化的实例,返回原实例if (holder == null) {holder = this;}return holder;}}}
/** * 子类必须在构造函数中调用父类的带参构造函数,完成反反射控制 * @author nathan * */
public class SubSingleton extends SubableSingleton {private static final long serialVersionUID = 2430773476223417288L;protected SubSingleton() {super(SubSingleton.class);}}

那么,你还认为单件简单吗?欢迎交流!

参考:

GOF的《设计模式》

《Effective Java》

《单件模式的7种写法》http://www.360doc.com/content/10/1213/09/2703996_77599342.shtml


附:相关单元测试

public class SingletonTest {@Testpublic void testSimpleSingleton() {Assert.assertEquals(SimpleSingleton.INSTANCE, SimpleSingleton.INSTANCE);}@Testpublic void testSimpleSingleton2() {Assert.assertEquals(SimpleSingleton2.getInstance(),SimpleSingleton2.getInstance());}@Testpublic void testAntiRefSingleton() throws Exception {Assert.assertEquals(AntiRefSingleton.instance,AntiRefSingleton.instance);try {Constructor<AntiRefSingleton> constructor = AntiRefSingleton.class.getDeclaredConstructor();constructor.setAccessible(true);constructor.newInstance();} catch (Exception e) {e.printStackTrace();}}@Testpublic void testLazySingleton() {Assert.assertEquals(LazySingleton.getInstance(),LazySingleton.getInstance());}@Testpublic void testConcurrentSingleton() {Assert.assertEquals(ConcurrentSingleton.getInstance(),ConcurrentSingleton.getInstance());}@Testpublic void testSubableSingleton() {Assert.assertNotNull(SubableSingleton.getInstance(SubableSingleton.class));Assert.assertTrue(SubableSingleton.getInstance(SubableSingleton.class) instanceof SubableSingleton);Assert.assertEquals(SubableSingleton.getInstance(SubableSingleton.class),SubableSingleton.getInstance(SubableSingleton.class));Assert.assertNotNull(SubableSingleton.getInstance(SubSingleton.class));Assert.assertTrue(SubableSingleton.getInstance(SubSingleton.class) instanceof SubSingleton);Assert.assertEquals(SubableSingleton.getInstance(SubSingleton.class),SubableSingleton.getInstance(SubSingleton.class));}@Testpublic void testSubableSingletonSerialize() throws Exception {SubableSingleton instance = SubableSingleton.getInstance(SubableSingleton.class);SubableSingleton instance2 = SubableSingleton.getInstance(SubSingleton.class);instance.doSomething();instance2.doSomething();SubableSingleton.serialize("testSubableSingletonSerialize.jser");SubableSingleton.antiSerialize("testSubableSingletonSerialize.jser");Assert.assertEquals(instance,SubableSingleton.getInstance(SubableSingleton.class));Assert.assertEquals(instance2,SubableSingleton.getInstance(SubSingleton.class));}@Testpublic void testSubableSingletonSerialize2() throws Exception {SubableSingleton.antiSerialize("testSubableSingletonSerialize.jser");SubableSingleton instance = SubableSingleton.getInstance(SubableSingleton.class);SubableSingleton instance2 = SubableSingleton.getInstance(SubSingleton.class);Assert.assertEquals(100, instance.getCount());Assert.assertEquals(100, instance2.getCount());instance.setCount(20);SubableSingleton.antiSerialize("testSubableSingletonSerialize.jser");instance = SubableSingleton.getInstance(SubableSingleton.class);Assert.assertEquals(20, instance.getCount());}@Testpublic void testSubableSingletonSerialize3() throws Exception {SubableSingleton instance = SubableSingleton.getInstance(SubableSingleton.class);instance.doSomething();ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("testSubableSingletonSerialize.jser")));oos.writeObject(instance);ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("testSubableSingletonSerialize.jser")));SubableSingleton instance2 = (SubableSingleton) ois.readObject();Assert.assertEquals(instance, instance2);}@Testpublic void testSubableSingletonSerialize4() throws Exception {ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("testSubableSingletonSerialize.jser")));SubableSingleton instance2 = (SubableSingleton) ois.readObject();Assert.assertEquals(100, instance2.getCount());instance2.setCount(20);ois = new ObjectInputStream(new FileInputStream(new File("testSubableSingletonSerialize.jser")));instance2 = (SubableSingleton) ois.readObject();Assert.assertEquals(20, instance2.getCount());}@Testpublic void testSubableSingletonSerialize5() throws Exception {SubableSingleton instance = SubableSingleton.getInstance(SubSingleton.class);SubableSingleton instance2 = SubableSingleton.getInstance(SubSingleton.class);instance.doSomething();instance2.doSomething();SubableSingleton.serialize("testSubableSingletonSerialize5.jser");SubableSingleton.antiSerialize("testSubableSingletonSerialize5.jser");Assert.assertEquals(instance,SubableSingleton.getInstance(SubSingleton.class));Assert.assertEquals(instance2,SubableSingleton.getInstance(SubSingleton.class));Assert.assertEquals(100, instance2.getCount());}@Testpublic void testSerializableSingletonInOneJvm() throws IOException,ClassNotFoundException {Assert.assertEquals(SerializableSingleton.getInstance(),SerializableSingleton.getInstance());SerializableSingleton singleton = SerializableSingleton.getInstance();singleton.doSomething();System.out.println(singleton);ByteArrayOutputStream baos = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(baos);oos.writeObject(singleton);ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());ObjectInputStream ois = new ObjectInputStream(bais);SerializableSingleton clone = (SerializableSingleton) ois.readObject();System.out.println(clone);Assert.assertEquals(singleton, clone);clone.doSomething();}/** * 在同一个jvm中,序列化后再反序列化对单件来说是无效的,jvm中始终使用的是最初创建的那个单件实例 *  * @throws Exception */@Testpublic void testSerializableSingleton2InOneJvm() throws Exception {Assert.assertEquals(SerializableSingleton2.getInstance(),SerializableSingleton2.getInstance());SerializableSingleton2 singleton = SerializableSingleton2.getInstance();singleton.doSomething();Assert.assertEquals(SerializableSingleton2.getInstance().getCount(),100);SerializableSingleton2.serialize("SerializableSingleton2.jser");// 序列化后改变实例数据SerializableSingleton2.getInstance().setCount(30);// 反序列化,实际上并未使用反序列化出来的实例,而是继续使用原来的实例,因为在同一个Jvm中SerializableSingleton2.antiSerialize("SerializableSingleton2.jser");// 因此这里的值是30,不是序列化时候的100Assert.assertEquals(SerializableSingleton2.getInstance().getCount(), 30);// 改变实例数据SerializableSingleton2.getInstance().setCount(20);// 再反序列化SerializableSingleton2.antiSerialize("SerializableSingleton2.jser");Assert.assertEquals(SerializableSingleton2.getInstance().getCount(), 20);}/** * 在另一个jvm中启动反序列化<br> * 注意:请先执行测试testSerializableSingleton2InOneJvm,再执行该测试 *  * @throws Exception */@Testpublic void testSerializableSingleton2NotInOneJvmRead() throws Exception {// 反序列化,并创建单件实例,此后在该jvm中将一直使用该实例SerializableSingleton2.antiSerialize("SerializableSingleton2.jser");// 因此这里的值是序列化时候的100Assert.assertEquals(SerializableSingleton2.getInstance().getCount(),100);// 改变实例数据SerializableSingleton2.getInstance().setCount(20);// 再反序列化SerializableSingleton2.antiSerialize("SerializableSingleton2.jser");Assert.assertEquals(SerializableSingleton2.getInstance().getCount(), 20);}}


原创粉丝点击