以下单例模式利用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));
}