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(); } } 复制代码
它的原理是利用了类加载机制。
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条指令:
而Unsafe.allocateInstance()方法值做了第一步和第二步,即分配内存空间,返回内存地址,没有做第三步调用构造函数。所以Unsafe.allocateInstance()方法创建的对象都是只有初始值,没有默认值也没有构造函数设置的值, 因为它完全没有使用new机制,绕过了构造函数直接操作内存创建了对象,而单例是通过私有化构造函数来保证的,这就使得单例失败 。
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; } 复制代码
最后总结一下静态内部类写法:
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代码块来保证单例(在类加载的时候就初始化了)。
//反射 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层面保证了枚举不能被反射。
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 这个名称输出,反序列化的时候再通过这个名称,查找对应的枚举类型,因此反序列化后的实例也会和之前被序列化的对象实例相同。
public class Singleton { private Singleton(){} private static final ThreadLocal<Singleton> threadLocal = new ThreadLocal<Singleton>(){ @Override protected Singleton initialValue(){ return new Singleton(); } }; public static Singleton getInstance(){ return threadLocal.get(); } } 复制代码
这种写法利用了ThreadLocal的特性,可以保证局部单例,即在各自的线程中是单例的,但是线程与线程之间不保证单例。
package com.baomidou.dynamic.datasource.toolkit; import java.util.concurrent.LinkedBlockingDeque; public final class DynamicDataSourceContextHolder { //重点!!! private static final ThreadLocal<LinkedBlockingDeque<String>> LOOKUP_KEY_HOLDER = new ThreadLocal() { protected Object initialValue() { return new LinkedBlockingDeque(); } private DynamicDataSourceContextHolder() { } public static String getDataSourceLookupKey() { LinkedBlockingDeque<String> 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<String> deque = (LinkedBlockingDeque)LOOKUP_KEY_HOLDER.get(); if (deque.isEmpty()) { LOOKUP_KEY_HOLDER.remove(); } else { deque.pollFirst(); } } }; } 复制代码
PS:initialValue()一般是用来在使用时进行重写的,如果在没有set的时候就调用get,会调用initialValue方法初始化内容。