2.3 使用单例

以下单例模式利用JVM的类加载机制保证单例对象的线程安全和初始化(在第一次类被加载时,会初始化INSTANCE属性,进而创建单例对象)

public class SingletonOne {
	//在类加载后立即初始化
	public static final SingletonOne INSTANCE=new SingletonOne();
	
	private SingletonOne(){}
}

 

为了符合一般约定,也可以提供getInstance()方法返回单例对象

public class SingletonTwo {
	private static final SingletonTwo INSTANCE=new SingletonTwo();
	
	private SingletonTwo(){}
	
	public static SingletonTwo getInstance(){
		return INSTANCE;
	}
}

 

也可以使用Enum创建单例对象,如下(注:所有枚举类都继承java.lang.Enum类):

public enum SingletonThree {
	INSTANCE;//单例对象,唯一的枚举量
	
	private SingletonThree(){
		//构造函数,可在此初始化单例对象
	}
}

 

以下为懒汉式单例设计模式,即在第一次调用getInstance()才会初始化单例对象(第一次调用会导致加载静态内部类,进而创建单例对象)。如果创建单例对象比较消耗资源,那么可以使用此方式

public class SingletonFour {
	//使用静态内部类存储单例对象
	private static final class SingletonHolder{
		private static final SingletonFour INSTANCE=new SingletonFour();
	}
	
	private SingletonFour(){}
	
	public static SingletonFour getInstance(){
		return SingletonHolder.INSTANCE;
	}
	
}

 

 

其它创建单例的方法还包括使用synchronized,使用双重锁检查(适用于JDK1.5)。这些方法需要进行线程同步,或者代码不够简洁,不推荐使用

 

如果调用AccessibleObject.setAccessible(),那么可以访问私有方法(包括私有构造函数),进而可创建另一个单例对象,如下测试将失败

	@Test
	public void testSingletonAccessible() throws Exception{
		Constructor<SingletonOne> constructor=SingletonOne.class.getDeclaredConstructor();
		constructor.setAccessible(true);//调用此方法后,将可以访问私有构造函数
		SingletonOne singletonOne=constructor.newInstance();//通过反射创建另一个单例对象
		
		Assert.assertEquals(singletonOne, SingletonOne.INSTANCE);
	}

 

为了解决以上问题,需要修改单例类的私有构造函数,如下:

public class SingletonOne {
	//在类加载后被初始化
	public static final SingletonOne INSTANCE=new SingletonOne();
	
	private SingletonOne(){
		//如果单例对象已存在,则抛出异常
		if(INSTANCE!=null)
			throw new IllegalAccessError("This is Singleton");
	}
}

 

如果单例类实现了Serializable接口,那么可以通过反序列化创建另一个单例对象,如下测试将失败:

	@Test
	public void testSingletonSerialization() throws Exception{
		File file=new File("./singleton.out");
		
		//序列化单例对象
		ObjectOutput out=new ObjectOutputStream(new FileOutputStream(file));
		out.writeObject(SingletonOne.INSTANCE);
		out.close();
		
		//反序列化单例对象
		ObjectInput in=new ObjectInputStream(new FileInputStream(file));
		SingletonOne singletonOne=(SingletonOne)in.readObject();
		in.close();
		
		Assert.assertEquals(singletonOne, SingletonOne.INSTANCE);
	}

 

为了解决以上办法,可以添加readResolve()方法。这样通过反序列化创建的对象将被直接丢弃:

注:反序列化创建对象时不会调用类的任何构造函数,如果类实现Externalizable接口则会调用。

public class SingletonOne implements Serializable {
	//在类加载后被初始化
	public static final SingletonOne INSTANCE=new SingletonOne();
	
	private SingletonOne(){
		//如果单例对象已存在,则抛出异常
		if(INSTANCE!=null)
			throw new IllegalAccessError("This is Singleton");
	}

	private Object readResolve(){
		//直接返回已经创建的单列对象,避免使用反序列化时创建新的对象
		return INSTANCE;
	}
}

 

此外,如果使用两个不同的ClassLoader去加载同一个单例类,那么也可能创建两个不同的单例对象。暂时不知道如何测试,网上给出的解决方法给单例类如下代码:

	private static Class getClass(String classname)
			throws ClassNotFoundException {
		ClassLoader classLoader = Thread.currentThread()
				.getContextClassLoader();

		if (classLoader == null)
			classLoader = SingletonOne.class.getClassLoader();

		return (classLoader.loadClass(classname));
	}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值