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();
}
}
複製代碼
它的原理是利用了類加載機制。java
Class clazz = InnerClassSingleton.class;
Constructor c = clazz.getDeclaredConstructor(null);
c.setAccessible(true);
Object o1 = c.newInstance();
Object o2 = InnerClassSingleton.getInstance();
複製代碼
執行這段代碼會發現o1<>o2,這就破壞了單例。
爲何呢?罪魁禍首就是以下代碼,它是反射的newInstance()的底層實現。bash
UnsafeFieldAccessorImpl.unsafe.allocateInstance(class)
複製代碼
咱們知道new建立對象時會被編譯成3條指令:ide
而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()的源碼:ui
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 InnerClassHelper.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;
}
複製代碼
最後總結一下靜態內部類寫法:this
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;
}
}
複製代碼
反編譯這段代碼,獲得:spa
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層面保證了枚舉不能被反射。code
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方法初始化內容。