1、静态内部类
public class InnerClassSingleton implements Serializable { //无参构造函数 private InnerClassSingleton(){}; public static final InnerClassSingleton getInstance(){ return InnerClassHelper.INSTANCE; } //内部类 private static class InnerClassHelper{ private static final InnerClassSingleton INSTANCE = new InnerClassSingleton(); }}
它的原理是利用了类加载机制。
1.1、但是它可以被反射破坏
Class clazz = InnerClassSingleton.class; Constructor c = clazz.getDeclaredConstructor(null); c.setAccessible(true); Object o1 = c.newInstance(); Object o2 = InnerClassSingleton.getInstance();
执行这段代码会发现o1<>o2,这就破坏了单例。
为什么呢?罪魁祸首就是如下代码,它是反射的newInstance()的底层实现。UnsafeFieldAccessorImpl.unsafe.allocateInstance(class)
我们知道new创建对象时会被编译成3条指令:
- 1、根据类型分配一块内存区域
- 2、把第一条指令返回的内存地址压入操作数栈顶
- 3、调用类的构造函数
而Unsafe.allocateInstance()方法值做了第一步和第二步,即分配内存空间,返回内存地址,没有做第三步调用构造函数。所以Unsafe.allocateInstance()方法创建的对象都是只有初始值,没有默认值也没有构造函数设置的值,因为它完全没有使用new机制,绕过了构造函数直接操作内存创建了对象,而单例是通过私有化构造函数来保证的,这就使得单例失败。
1.2、还可以被反序列化破坏
InnerClassSingleton o1 = null;InnerClassSingleton o2 = InnerClassSingleton.getInstance();FileOutputStream fos = new FileOutputStream("InnerClassSingleton.obj");ObjectOutputStream oos = new ObjectOutputStream(fos);oos.writeObject(o2);oos.flush();oos.close();FileInputStream fis = new FileInputStream("InnerClassSingleton.obj");ObjectInputStream ois = new ObjectInputStream(fis);o1 = (InnerClassSingleton) ois.readObject();ois.close();System.out.println(o1);System.out.println(o2);
执行完这段代码我们又会发现o1<>o2,可见通过反序列化,成功破坏了单例,创建了2个对象。
那么如何避免这种情况发生呢?很简单,只要在代码中添加:public class InnerClassSingleton implements Serializable { ....省略重复代码 private Object readResolve(){ return InnerClassHelper.INSTANCE; }}
这时候我们可以再执行一下上面反序列化的方法,会很神奇的发现o1==o2,那这是为什么呢?我们一起来看下ois.readObject()的源码:
private Object readObject0(boolean unshared) throws IOException { ...省略 case TC_OBJECT: return checkResolve(readOrdinaryObject(unshared));}-------------------------------------------------------------------private Object readOrdinaryObject(boolean unshared){ if (bin.readByte() != TC_OBJECT) { throw new InternalError(); } ObjectStreamClass desc = readClassDesc(false); desc.checkDeserialize(); Class cl = desc.forClass(); if (cl == String.class || cl == Class.class || cl == ObjectStreamClass.class) { throw new InvalidClassException("invalid class descriptor"); } Object obj; try { //重点!!! //首先isInstantiable()判断是否可以初始化 //如果为true,则调用newInstance()方法创建对象,这时创建的对象是不走构造函数的,是一个新的对象 obj = desc.isInstantiable() ? desc.newInstance() : null; } catch (Exception ex) { throw (IOException) new InvalidClassException( desc.forClass().getName(), "unable to create instance").initCause(ex); } passHandle = handles.assign(unshared ? unsharedMarker : obj); ClassNotFoundException resolveEx = desc.getResolveException(); if (resolveEx != null) { handles.markException(passHandle, resolveEx); } if (desc.isExternalizable()) { readExternalData((Externalizable) obj, desc); } else { readSerialData(obj, desc); } handles.finish(passHandle); //重点!!! //hasReadResolveMethod()会去判断,我们的InnerClassSingleton对象中是否有readResolve()方法 if (obj != null && handles.lookupException(passHandle) == null && desc.hasReadResolveMethod()) { //如果为true,则执行readResolve()方法,而我们在自己的readResolve()方法中 直接retrun INSTANCE,所以还是返回的同一个对象,保证了单例 Object rep = desc.invokeReadResolve(obj); if (unshared && rep.getClass().isArray()) { rep = cloneArray(rep); } if (rep != obj) { // Filter the replacement object if (rep != null) { if (rep.getClass().isArray()) { filterCheck(rep.getClass(), Array.getLength(rep)); } else { filterCheck(rep.getClass(), -1); } } handles.setObject(passHandle, obj = rep); } } return obj;}
最后总结一下静态内部类写法:
- 优点:不用synchronized,性能好;简单
- 缺点:无法避免被反射、反序列化破坏
2、枚举
public enum EnumSingleton { INSTANCE; private Object data; public Object getData() { return data; } public void setData(Object data) { this.data = data; } public static EnumSingleton getInstance() { return INSTANCE; }}
反编译这段代码,得到:
static { INSTANCE = new EnumSingleton("INSTANCE",0); $VALUE = (new EnumSingleton[] { INSTANCE }); }
显然这是一种饿汉式的写法,用static代码块来保证单例(在类加载的时候就初始化了)。
2.1、可以避免被反射破坏
//反射Class clazz = EnumSingleton.class;//拿到构造函数Constructor c = clazz.getDeclaredConstructor(String.class, int.class);c.setAccessible(true);EnumSingleton instance1 = (EnumSingleton)c.newInstance("smart", 111);-----------------------------------------------------------------------------------------public T newInstance(Object ... initargs){ if ((clazz.getModifiers() & Modifier.ENUM) != 0) throw new IllegalArgumentException("Cannot reflectively create enum objects");}
可以看到,在newInstance()方法中,做了类型判断,如果是枚举类型,直接抛出异常。也就是说从jdk层面保证了枚举不能被反射。
2.2、可以避免被反序列化破坏
Java规范中规定,每一个枚举类型极其定义的枚举变量在JVM中都是唯一的,在序列化的时候Java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过 java.lang.Enum 的 valueOf() 方法来根据名字查找枚举对象。
...省略EnumSingleton o1 = (EnumSingleton) ois.readObject();-----------------------------------------------------------------------------------private Object readObject0(boolean unshared) throws IOException { ...省略 case TC_ENUM: return checkResolve(readEnum(unshared));}-------------------------------------------------------------------private Object readEnum(boolean unshared){ ...省略 String name = readString(false); Enum result = null; Class cl = desc.forClass(); if (cl != null) { try { @SuppressWarnings("unchecked") //重点!!! //通过valueOf方法获取Enum,参数为class和name Enum en = Enum.valueOf((Class)cl, name); result = en; } catch (IllegalArgumentException ex) { throw (IOException) new InvalidObjectException( "enum constant " + name + " does not exist in " + cl).initCause(ex); } if (!unshared) { handles.setObject(enumHandle, result); } }}
所以序列化的时候只将 INSTANCE 这个名称输出,反序列化的时候再通过这个名称,查找对应的枚举类型,因此反序列化后的实例也会和之前被序列化的对象实例相同。
3、ThreadLocal单例模式
public class Singleton { private Singleton(){} private static final ThreadLocalthreadLocal = new ThreadLocal (){ @Override protected Singleton initialValue(){ return new Singleton(); } }; public static Singleton getInstance(){ return threadLocal.get(); } }
这种写法利用了ThreadLocal的特性,可以保证局部单例,即在各自的线程中是单例的,但是线程与线程之间不保证单例。
应用场景(在Spring的第三方包baomidou的多数据源中,有用到这种写法):
package com.baomidou.dynamic.datasource.toolkit;import java.util.concurrent.LinkedBlockingDeque;public final class DynamicDataSourceContextHolder { //重点!!! private static final ThreadLocal> LOOKUP_KEY_HOLDER = new ThreadLocal() { protected Object initialValue() { return new LinkedBlockingDeque(); } private DynamicDataSourceContextHolder() { } public static String getDataSourceLookupKey() { LinkedBlockingDeque deque = (LinkedBlockingDeque)LOOKUP_KEY_HOLDER.get(); return deque.isEmpty() ? null : (String)deque.getFirst(); } public static void setDataSourceLookupKey(String dataSourceLookupKey) { ((LinkedBlockingDeque)LOOKUP_KEY_HOLDER.get()).addFirst(dataSourceLookupKey); } public static void clearDataSourceLookupKey() { LinkedBlockingDeque deque = (LinkedBlockingDeque)LOOKUP_KEY_HOLDER.get(); if (deque.isEmpty()) { LOOKUP_KEY_HOLDER.remove(); } else { deque.pollFirst(); } } };}
PS:initialValue()一般是用来在使用时进行重写的,如果在没有set的时候就调用get,会调用initialValue方法初始化内容。