【读书笔记】HeadFirst设计模式——单件不简单:详述实现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";
	}

	@Override
	public 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;
	}

	@Override
	public 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;
	}

	@Override
	public 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 {
	@Test
	public void testSimpleSingleton() {
		Assert.assertEquals(SimpleSingleton.INSTANCE, SimpleSingleton.INSTANCE);
	}

	@Test
	public void testSimpleSingleton2() {
		Assert.assertEquals(SimpleSingleton2.getInstance(),
				SimpleSingleton2.getInstance());
	}

	@Test
	public 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();
		}
	}

	@Test
	public void testLazySingleton() {
		Assert.assertEquals(LazySingleton.getInstance(),
				LazySingleton.getInstance());
	}

	@Test
	public void testConcurrentSingleton() {
		Assert.assertEquals(ConcurrentSingleton.getInstance(),
				ConcurrentSingleton.getInstance());
	}

	@Test
	public 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));
	}

	@Test
	public 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));
	}

	@Test
	public 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());
	}

	@Test
	public 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);

	}

	@Test
	public 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());
	}

	@Test
	public 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());
	}

	@Test
	public 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
	 */
	@Test
	public 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,不是序列化时候的100
		Assert.assertEquals(SerializableSingleton2.getInstance().getCount(), 30);

		// 改变实例数据
		SerializableSingleton2.getInstance().setCount(20);

		// 再反序列化
		SerializableSingleton2.antiSerialize("SerializableSingleton2.jser");
		Assert.assertEquals(SerializableSingleton2.getInstance().getCount(), 20);
	}

	/**
	 * 在另一个jvm中启动反序列化<br>
	 * 注意:请先执行测试testSerializableSingleton2InOneJvm,再执行该测试
	 * 
	 * @throws Exception
	 */
	@Test
	public void testSerializableSingleton2NotInOneJvmRead() throws Exception {
		// 反序列化,并创建单件实例,此后在该jvm中将一直使用该实例
		SerializableSingleton2.antiSerialize("SerializableSingleton2.jser");
		// 因此这里的值是序列化时候的100
		Assert.assertEquals(SerializableSingleton2.getInstance().getCount(),
				100);

		// 改变实例数据
		SerializableSingleton2.getInstance().setCount(20);

		// 再反序列化
		SerializableSingleton2.antiSerialize("SerializableSingleton2.jser");
		Assert.assertEquals(SerializableSingleton2.getInstance().getCount(), 20);
	}

}


你可能感兴趣的:(Singleton)