指確保一個類在任何狀況下都絕對只有一個實例,並提供一個全局訪問點java
餓漢式單例是在類加載的時候就當即初始化,而且建立單例對象.絕對線程安全,在線程還沒出現之前就是實例化了,不可能存在訪問安全問題緩存
public class HungrySingleton { private static final HungrySingleton hungrySingleton = new HungrySingleton(); private HungrySingleton() {} public static HungrySingleton getInstance() { return hungrySingleton; } }
public class HungryStaticSingleton { private static final HungryStaticSingleton hungrySingleton; private HungryStaticSingleton() {} static { hungrySingleton = new HungryStaticSingleton(); } public static HungryStaticSingleton getInstance() { return hungrySingleton; } }
當類被外部調用時才建立實例安全
懶漢式單例分爲如下幾種:多線程
public class LazySimpleSingleton { private static LazySimpleSingleton lazySimpleSingleton = null; private LazySimpleSingleton(){} public static LazySimpleSingleton getInstance() { if (null == lazySimpleSingleton) { lazySimpleSingleton = new LazySimpleSingleton(); } return lazySimpleSingleton; } }
在 IDEA 中用線程模式調試,手動控制線程的執行順序來跟蹤內存的變化狀態,給 LazySimpleSingleton 加上斷點,選擇 Thread 模式併發
debug 啓動測試類LazySimpleSingletonTest
,線程 0 和 1 進入斷點,在 debug 中將線程控制下拉框中切換至線程 0,執行下一行代碼後停住app
切換至線程 1 執行下一行代碼後停住,LazySimpleSingleton
類被實例化了兩次,這樣就破壞了單例模式ide
輸出結果,LazySimpleSingleton 實例化兩個對象工具
多線程類源碼分析
public class ExecutorThread implements Runnable { @Override public void run() { LazySimpleSingleton lazySimpleSingleton = LazySimpleSingleton.getInstance(); System.out.println(Thread.currentThread().getName() + ":" + lazySimpleSingleton); } }
多線程測試類測試
public class LazySimpleSingletonTest { public static void main(String[] args) { Thread thread1 = new Thread(new ExecutorThread()); Thread thread2 = new Thread(new ExecutorThread()); thread1.start(); thread2.start(); System.out.println("執行結束"); } }
簡單懶漢式單例
存在的線程安全問題public class LazyDoubleCheckSingleton { private static LazyDoubleCheckSingleton lazyDoubleCheckSingleton = null; private LazyDoubleCheckSingleton(){} public static LazyDoubleCheckSingleton getInstance() { if (null == lazyDoubleCheckSingleton) { synchronized (LazyDoubleCheckSingleton.class) { if (null == lazyDoubleCheckSingleton) { lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton(); } } } return lazyDoubleCheckSingleton; } }
在 IDEA 中用線程模式調試,手動控制線程的執行順序來跟蹤內存的變化狀態,給 LazyDoubleCheckSingleton 加上斷點,選擇 Thread 模式
debug 啓動測試類LazyDoubleCheckSingletonTest
,線程 0 和 1 進入斷點,此時線程 0 和 1 都是 RUNNING
在 debug 中將線程控制下拉框中切換至線程 0,執行下一行代碼後停住,此時線程 0 和 1 都是 RUNNING
線程再切換至線程 1 執行下一行代碼,此時線程 0 是 RUNNING,線程 1 是 MONITOR,線程 0 拿到鎖的狀況下,線程 1 沒法進入建立實例代碼區域
直到線程 0 執行完釋放鎖線程 1 才能執行代碼,此時線程 0 和 1 都是 RUNNING,lazyDoubleCheckSingleton 對象已經建立,if(null==lazyDoubleCheckSingleton)
因條件不成立而不進行實例建立,這樣線程就是安全的
執行結果,LazyDoubleCheckSingleton 類只被實例化了一次
if(null!=LazyHolder.LAZY)
解決反射問題/** * 靜態內部類懶漢式單例 */ public class LazyInnerClassSingleton { private LazyInnerClassSingleton(){} // LazyHolder 裏面的邏輯須要等到外部方法調用時才執行 // 全程沒有用到 synchronized // 巧妙利用了內部類的特性 // JVM 底層執行邏輯,完美的避免了線程安全問題 public static LazyInnerClassSingleton getInstance() { return LazyHolder.LAZY; } private static class LazyHolder { private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton(); } }
以前的餓漢和懶漢單例模式的構造方法除了加上 private,若是咱們使用反射來調用其構造方法,而後再調用 getInstance()方法,應該就會兩個不一樣的實例
public class LazyInnerClassSingletonTest { public static void main(String[] args) throws NoSuchMethodException { try { Class<?> clazz = LazyInnerClassSingleton.class; Constructor constructor = clazz.getDeclaredConstructor(null); constructor.setAccessible(true); Object object1 = constructor.newInstance(); Object object2 = LazyInnerClassSingleton.getInstance(); System.out.println(object1 == object2); } catch (Exception e) { e.printStackTrace(); } } }
運行結果爲 false,object1 是經過反射建立的,object2 是一般正常建立的,這是兩個指向不一樣內存地址的對象,破壞了單例
在私有構造方法中增長判斷能防止反射破壞單例
private LazyInnerClassSingleton(){ /** * 在私有構造方法中增長判斷能防止反射破壞單例 */ if (null != LazyHolder.LAZY) { throw new RuntimeException("禁止反射建立實例"); } }
/** * 靜態內部類懶漢式單例 */ public class LazyInnerClassSingleton { private LazyInnerClassSingleton(){ /** * 在私有構造方法中增長判斷能防止反射破壞單例 */ if (null != LazyHolder.LAZY) { throw new RuntimeException("禁止反射建立實例"); } } // LazyHolder 裏面的邏輯須要等到外部方法調用時才執行 // 全程沒有用到 synchronized // 巧妙利用了內部類的特性 // JVM 底層執行邏輯,完美的避免了線程安全問題 public static LazyInnerClassSingleton getInstance() { return LazyHolder.LAZY; } private static class LazyHolder { private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton(); } }
運行結果
經過序列化將對象輸出到文件,再經過反序列化將文件加載到內存,這樣單例實例將在內存中存在兩個對象,從而破壞單例模式
餓漢模式的單例
public class SerializableSingleton implements Serializable { private static final SerializableSingleton serializableSingleton = new SerializableSingleton(); private SerializableSingleton() {} public static SerializableSingleton getInstance() { return serializableSingleton; } }
序列化測試類
public class SerializableSingletonTest { public static void main(String[] args) { SerializableSingleton s1 = null; SerializableSingleton s2 = SerializableSingleton.getInstance(); FileOutputStream fileOutputStream = null; FileInputStream fileInputStream = null; ObjectOutputStream objectOutputStream = null; ObjectInputStream objectInputStream = null; try { fileOutputStream = new FileOutputStream("./singleton/SerializableSingleton.obj"); objectOutputStream = new ObjectOutputStream(fileOutputStream); objectOutputStream.writeObject(s2); objectOutputStream.flush(); objectOutputStream.close(); fileInputStream = new FileInputStream("./singleton/SerializableSingleton.obj"); objectInputStream = new ObjectInputStream(fileInputStream); s1 = (SerializableSingleton) objectInputStream.readObject(); objectInputStream.close(); System.out.println(s1 == s2); } catch (Exception e) { e.printStackTrace(); } finally { IOUtils.closeQuietly(objectInputStream); IOUtils.closeQuietly(objectOutputStream); IOUtils.closeQuietly(fileInputStream); IOUtils.closeQuietly(fileOutputStream); } } }
輸出結果爲兩個對象 s1,s2 不相等,SerializableSingleton 對象實例化了兩個對象,分別指向不一樣的內存地址
在單例類中重寫readResolve()
方法
public class SerializableSingleton implements Serializable { private static final SerializableSingleton serializableSingleton = new SerializableSingleton(); private SerializableSingleton() {} public static SerializableSingleton getInstance() { return serializableSingleton; } // 重寫 readResolve 方法只不過是覆蓋了反序列化出來的對象 // 仍是建立了兩次,只不過是發生在 JVM 層面,相對來講說比較安全 // 以前反序列化出來的對象會被 GC 回收 private Object readResolve() { return serializableSingleton; } }
在序列化測試類的代碼中s1=(SerializableSingleton)objectInputStream.readObject()
,查看readObject()
方法源碼
public final Object readObject() throws IOException, ClassNotFoundException { if (enableOverride) { return readObjectOverride(); } // if nested read, passHandle contains handle of enclosing object int outerHandle = passHandle; try { Object obj = readObject0(false); handles.markDependency(outerHandle, passHandle); ClassNotFoundException ex = handles.lookupException(passHandle); if (ex != null) { throw ex; } if (depth == 0) { vlist.doCallbacks(); } return obj; } finally { passHandle = outerHandle; if (closed && depth == 0) { clear(); } } }
在readObject()
源碼中能夠查看到readObject0()
方法,在TC_OBJECT
中調用readOrdinaryObject()
private Object readObject0(boolean unshared) throws IOException { ... case TC_OBJECT: return checkResolve(readOrdinaryObject(unshared)); ... }
在readOrdinaryObject()
方法中desc.isInstantiable()
判斷是否存在構造方法
private Object readOrdinaryObject(boolean unshared) throws IOException { ... Object obj; try { obj = desc.isInstantiable() ? desc.newInstance() : null; } catch (Exception ex) { ... if (obj != null && handles.lookupException(passHandle) == null && desc.hasReadResolveMethod()) { 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); } } }
調用了 ObjectStreamClass 的 isInstantiable()方法,而 isInstantiable()裏面的代碼,判斷一下構造方法是否爲空,構造方法不爲空就返回 true,意味着只要有無參構造方法就會實例化
boolean isInstantiable() { requireInitialized(); return (cons != null); }
判斷無參構造方法是否存在以後,又調用了 hasReadResolveMethod()方法
boolean hasReadResolveMethod() { requireInitialized(); return (readResolveMethod != null); }
就是判斷 readResolveMethod 是否爲空,不爲空就返回 true,經過全局查找找到了 ObjectStreamClass 中對 readResolveMethod 賦值代碼在私有方法
readResolveMethod = getInheritableMethod(cl, "readResolve", null, Object.class);
在代碼能夠看到這樣一行代碼if(obj!=null&&handles.lookupException(passHandle)==null&&desc.hasReadResolveMethod())
,若是這個判斷返回爲 true,將能執行desc.invokeReadResolve(obj)
調用序列化對象中的 readResolve 方法
desc.hasReadResolveMethod()返回 true
private Object readOrdinaryObject(boolean unshared) throws IOException {
...
if (obj != null && handles.lookupException(passHandle) == null && desc.hasReadResolveMethod())
{
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);
}
}
}
invokeReadResolve()
方法中用反射調用了readResolveMethod
方法
Object invokeReadResolve(Object obj) throws IOException, UnsupportedOperationException { requireInitialized(); if (readResolveMethod != null) { try { return readResolveMethod.invoke(obj, (Object[]) null); } catch (InvocationTargetException ex) { Throwable th = ex.getTargetException(); if (th instanceof ObjectStreamException) { throw (ObjectStreamException) th; } else { throwMiscException(th); throw new InternalError(th); // never reached } } catch (IllegalAccessException ex) { // should not occur, as access checks have been suppressed throw new InternalError(ex); } } else { throw new UnsupportedOperationException(); } }
經過 JDK 源碼分析咱們能夠看出,雖然增長readResolve()
方法返回實例,解決了單例被破壞的問題.可是咱們經過分析源碼以及調試,咱們能夠看到實際上實例化了兩次,只不過新建立的對象沒有被返回而已.那若是建立對象的動做發生頻率增大,就意味着內存分配開銷也就隨之增大,難道真的就沒辦法從根本上解決問題嗎?下面咱們來註冊式單例也許能幫助到你
註冊式單例又稱爲登記式單例,就是將每個實例都登記到某一個地方,使用惟一的標識獲取實例
註冊式單例有兩種寫法:
枚舉單例類
public enum EnumSingleton { INSTANCE; private Object data; public void setData(Object data) { this.data = data; } public Object getData() { return data; } public static EnumSingleton getInstance() { return INSTANCE; } }
反序列化測試類
public class EnumSingletonTest { public static void main(String[] args) { EnumSingleton s1 = null; EnumSingleton s2 = EnumSingleton.getInstance(); FileOutputStream fileOutputStream = null; FileInputStream fileInputStream = null; ObjectOutputStream objectOutputStream = null; ObjectInputStream objectInputStream = null; try { fileOutputStream = new FileOutputStream("./singleton/EnumSingleton.obj"); objectOutputStream = new ObjectOutputStream(fileOutputStream); objectOutputStream.writeObject(s2); objectOutputStream.flush(); objectOutputStream.close(); fileInputStream = new FileInputStream("./singleton/EnumSingleton.obj"); objectInputStream = new ObjectInputStream(fileInputStream); s1 = (EnumSingleton) objectInputStream.readObject(); objectInputStream.close(); System.out.println(s1 == s2); } catch (Exception e) { e.printStackTrace(); } finally { IOUtils.closeQuietly(objectInputStream); IOUtils.closeQuietly(objectOutputStream); IOUtils.closeQuietly(fileInputStream); IOUtils.closeQuietly(fileOutputStream); } } }
運行結果,兩個對象 s1,s2 指向同一塊內存地址,是同一個對象
這種方式沒有作任何處理卻能完美的解決序列化破壞單例,那麼枚舉式單例如此神奇,經過反編譯工具解開神祕面紗
在 IDEA 中找到 EnumSingleton 對應的 class 文件 EnumSingleton.class,複製所在路徑
而後切回到命令行,切換到工程所在的 Class 目錄,輸入命令 jad 後面輸入複製好的路徑,咱們會在 Class 目錄下會多一個 EnumSingleton.jad 文件.打開 EnumSingleton.jad 文件咱們驚奇又巧妙地發現有以下代碼:
static { INSTANCE = new EnumSingleton("INSTANCE", 0); $VALUES = (new EnumSingleton[] { INSTANCE }); }
原來枚舉式單例在靜態代碼塊中就給 INSTANCE 進行了賦值,是餓漢式單例的實現.咱們還能夠試想,序列化咱們可否破壞枚舉式單例呢?咱們不妨再來看一下 JDK 源碼,仍是回到 ObjectInputStream 的 readObject0()方法
private Object readObject0(boolean unshared) throws IOException { ... case TC_ENUM: return checkResolve(readEnum(unshared)); ... }
在 readObject0()中調用了 readEnum()方法,來看 readEnum()中代碼實現
private Enum<?> readEnum(boolean unshared) throws IOException { if (bin.readByte() != TC_ENUM) { throw new InternalError(); } ObjectStreamClass desc = readClassDesc(false); if (!desc.isEnum()) { throw new InvalidClassException("non-enum class: " + desc); } int enumHandle = handles.assign(unshared ? unsharedMarker : null); ClassNotFoundException resolveEx = desc.getResolveException(); if (resolveEx != null) { handles.markException(enumHandle, resolveEx); } String name = readString(false); Enum<?> result = null; Class<?> cl = desc.forClass(); if (cl != null) { try { @SuppressWarnings("unchecked") 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); } } handles.finish(enumHandle); passHandle = enumHandle; return result; }
發現枚舉類型其實經過類名和 Class 對象類找到一個惟一的枚舉對象,所以枚舉對象不可能被類加載器加載屢次
那麼反射是否能破壞枚舉式單例呢?來看一段測試代碼:
public static void main(String[] args) { try { Class clazz = EnumSingleton.class; Constructor c = clazz.getDeclaredConstructor(); c.newInstance(); }catch (Exception e){ e.printStackTrace(); } }
運行結果
報的是 java.lang.NoSuchMethodException 異常,意思是沒找到無參的構造方法.這時候,咱們打開 java.lang.Enum 的源碼代碼,查看它的構造方法,只有一個 protected 的構造方法,代碼以下:
protected Enum(String name, int ordinal) { this.name = name; this.ordinal = ordinal; }
那咱們再來作一個這樣的測試:
public static void main(String[] args) { try { Class clazz = EnumSingleton.class; Constructor c = clazz.getDeclaredConstructor(String.class,int.class); c.setAccessible(true); EnumSingleton enumSingleton = (EnumSingleton)c.newInstance("Tom",666); }catch (Exception e){ e.printStackTrace(); } }
運行結果
這時錯誤已經很是明顯了,告訴咱們 Cannotreflectivelycreateenumobjects,不能用反射來建立枚舉類型.仍是習慣性地想來看看 JDK 源碼,進入 Constructor 的 newInstance()方法:
public T newInstance(Object ... initargs) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { if (!override) { if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) { Class<?> caller = Reflection.getCallerClass(); checkAccess(caller, clazz, null, modifiers); } } if ((clazz.getModifiers() & Modifier.ENUM) != 0) { throw new IllegalArgumentException("Cannot reflectively create enum objects"); } ConstructorAccessor ca = constructorAccessor; if (ca == null) { ca = acquireConstructorAccessor(); } @SuppressWarnings("unchecked") T inst = (T) ca.newInstance(initargs); return inst; }
在 newInstance()方法中作了強制性的判斷,若是修飾符是 Modifier.ENUM 枚舉類型,直接拋出異常.到這爲止,咱們是否是已經很是清晰明瞭呢?枚舉式單例也是《EffectiveJava》書中推薦的一種單例實現寫法.在 JDK 枚舉的語法特殊性,以及反射也爲枚舉保駕護航,讓枚舉式單例成爲一種比較優雅的實現
容器式寫法適用於建立實例很是多的狀況,便於管理,是非線程安全的
public class ContainerSingleton { private ContainerSingleton(){} private static Map<String,Object> ioc = new ConcurrentHashMap<String,Object>(); public static Object getBean(String className){ synchronized (ioc) { if (!ioc.containsKey(className)) { Object obj = null; try { obj = Class.forName(className).newInstance(); ioc.put(className, obj); } catch (Exception e) { e.printStackTrace(); } return obj; } else { return ioc.get(className); } } } }
Spring 中的容器式單例的實現代碼
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory { /** Cache of unfinished FactoryBean instances: FactoryBean name --> BeanWrapper */ private final Map<String, BeanWrapper> factoryBeanInstanceCache = new ConcurrentHashMap<>(16); ... }
ThreadLocal 不能保證其建立的對象是全局惟一,可是能保證在單個線程中是惟一的,天生的線程安全.下面咱們來看代碼:
public class ThreadLocalSingleton { private static final ThreadLocal<ThreadLocalSingleton> threadLocalInstance = new ThreadLocal<ThreadLocalSingleton>() { @Override protected ThreadLocalSingleton initialValue() { return new ThreadLocalSingleton(); } }; private ThreadLocalSingleton() { } public static ThreadLocalSingleton getInstance() { return threadLocalInstance.get(); } }
多線程執行類
public class ThreadLocalExectorThread implements Runnable { @Override public void run() { ThreadLocalSingleton threadLocalSingleton = ThreadLocalSingleton.getInstance(); System.out.println(Thread.currentThread().getName() + ":" + threadLocalSingleton); } }
測試類
public class ThreadLocalSingletonTest { public static void main(String[] args) { System.out.println(ThreadLocalSingleton.getInstance()); System.out.println(ThreadLocalSingleton.getInstance()); System.out.println(ThreadLocalSingleton.getInstance()); System.out.println(ThreadLocalSingleton.getInstance()); System.out.println(ThreadLocalSingleton.getInstance()); Thread t1 = new Thread(new ThreadLocalExectorThread()); Thread t2 = new Thread(new ThreadLocalExectorThread()); t1.start(); t2.start(); } }
輸出結果
咱們發現,在主線程 main 中不管調用多少次,獲取到的實例都是同一個,都在兩個子線程中分別獲取到了不一樣的實例.那麼 ThreadLocal 是若是實現這樣的效果的呢?咱們知道上面的單例模式爲了達到線程安全的目的,給方法上鎖,以時間換空間.ThreadLocal 將全部的對象所有放在 ThreadLocalMap 中,爲每一個線程都提供一個對象,其實是以空間換時間來實現線程間隔離的