?單例模式
有些對(duì)象我們只需要一個(gè),比如線程池剧罩、ServletContext栓拜、ApplicationContext、 Windows 中的回收站惠昔,此時(shí)我們便可以用到單例模式幕与。
單例模式就是確保一個(gè)類(lèi)在任何情況下都只有一個(gè)實(shí)例,并提供一個(gè)全局訪問(wèn)點(diǎn)镇防。
1. 餓漢式單例
/**
* @author? java初學(xué)者組團(tuán)學(xué)習(xí)737251827
* 餓漢式單例
*/
public class HungrySingleton {
? ? //類(lèi)初始化的時(shí)候便進(jìn)行對(duì)象實(shí)例化
? ? private static final HungrySingleton hungrySingleton = new HungrySingleton();
? ? private HungrySingleton() {
? ? }
? ? public static HungrySingleton getInstance() {
? ? ? ? return hungrySingleton;
? ? }
}
優(yōu)點(diǎn):
餓漢式單例是最簡(jiǎn)單的一種單例形式啦鸣,它沒(méi)有添加任何的鎖,執(zhí)行效率最高
線程安全
缺點(diǎn):
某些情況下来氧,造成內(nèi)存浪費(fèi)诫给,因?yàn)閷?duì)象未被使用的情況下就會(huì)被初始化,如果一個(gè)項(xiàng)目中的類(lèi)多達(dá)上千個(gè)啦扬,在項(xiàng)目啟動(dòng)的時(shí)候便開(kāi)始初始化可能并不是我們想要的蝙搔。
2. 簡(jiǎn)單的懶漢式單例
想解決餓漢式單例一開(kāi)始就會(huì)進(jìn)行對(duì)象的初始化的問(wèn)題,一個(gè)很自然的想法就是當(dāng)用戶(hù)調(diào)用getInstance方法的時(shí)候再進(jìn)行實(shí)例的創(chuàng)建考传,修改代碼如下:
/**
* @author
* 餓漢式單例
*/
public class LazySimpleSingleton {
? ? private static LazySimpleSingleton instance;
? ? private LazySimpleSingleton() {
? ? }
? ? public static LazySimpleSingleton getInstance() {
? ? ? ? // 如果實(shí)例不存在吃型,則進(jìn)行初始化
? ? ? ? if (instance == null) {
? ? ? ? ? ? instance = new LazySimpleSingleton();
? ? ? ? }
? ? ? ? return instance;
? ? }
}
上述代碼在單線程下能夠完美運(yùn)行,但是在多線程下存在安全隱患僚楞。大家可以使用 IDEA 進(jìn)行手動(dòng)控制線程執(zhí)行順序來(lái)跟蹤內(nèi)存變化勤晚,下面我用圖解的形式進(jìn)行多線程下 3 種情形的說(shuō)明。
情形 1:
每個(gè)線程依次執(zhí)行 getInstance 方法泉褐,得到的結(jié)果正是我們所期望的
情形 2:
此種情形下赐写,該種寫(xiě)法的單例模式會(huì)出現(xiàn)多線程安全問(wèn)題,得到兩個(gè)完全不同的對(duì)象
情形 3:
該種情形下膜赃,雖然表面上最終得到的對(duì)象是同一個(gè)挺邀,但是在底層上其實(shí)是生成了 2 個(gè)對(duì)象,只不過(guò)是后者覆蓋了前者,不符合單例模式絕對(duì)只有一個(gè)實(shí)例的要求端铛。
3. 升級(jí)的懶漢式單例
/**
* @author
* 餓漢式單例-同步鎖
*/
public class LazySynchronizedSingleton {
? ? private static LazySynchronizedSingleton instance;
? ? private LazySynchronizedSingleton() {
? ? }
? ? //添加synchronized關(guān)鍵字
? ? public synchronized static LazySynchronizedSingleton getInstance() {
? ? ? ? if (instance == null) {
? ? ? ? ? ? instance = new LazySynchronizedSingleton();
? ? ? ? }
? ? ? ? return instance;
? ? }
}
升級(jí)之后的程序能完美地解決線程安全問(wèn)題泣矛。
但是用synchronized加鎖時(shí),在線程數(shù)量較多的情況下禾蚕,會(huì)導(dǎo)致大批線程阻塞您朽,從而導(dǎo)致程序性能大幅下降
有沒(méi)有一種形式,既能兼顧線程安全又能提升程序性能呢换淆?有哗总,這就是雙重檢查鎖。
4. 雙重檢查鎖
/**
* @author
* 雙重檢查鎖
*/
public class LazyDoubleCheck {
? ? // 需要添加 volatile 關(guān)鍵字
? ? private volatile static LazyDoubleCheck instance;
? ? private LazyDoubleCheck() {
? ? }
? ? public static LazyDoubleCheck getInstance() {
? ? ? ? //一重檢查:檢查實(shí)例倍试,如果不存在讯屈,進(jìn)入同步區(qū)塊
? ? ? ? if (instance == null) {
? ? ? ? ? ? synchronized (LazyDoubleCheck.class) {
? ? ? ? ? ? ? ? //雙重檢查:進(jìn)入同步區(qū)塊后,再檢查一次县习,如果仍然是null涮母,才創(chuàng)建實(shí)例
? ? ? ? ? ? ? ? if (instance == null) {
? ? ? ? ? ? ? ? ? ? instance = new LazyDoubleCheck();
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? return instance;
? ? }
}
第一重檢查是為了確認(rèn) instance 是否已經(jīng)被實(shí)例化,如果是准颓,則無(wú)需再進(jìn)入同步代碼塊哈蝇,直接返回實(shí)例化對(duì)象,否則進(jìn)入同步代碼塊進(jìn)行創(chuàng)建攘已,避免每次都排隊(duì)進(jìn)入同步代碼塊影響效率炮赦;
第二重檢查是真正與實(shí)例的創(chuàng)建相關(guān),如果instance未被實(shí)例化样勃,則在此過(guò)程中被實(shí)例化吠勘。
雙重檢查鎖版本的單例模式需要使用到volatile關(guān)鍵字,本文不對(duì)volatile關(guān)鍵字進(jìn)行深入分析峡眶,之后會(huì)單獨(dú)開(kāi)一篇文章進(jìn)行解釋
但是剧防,使用synchronized關(guān)鍵總歸是要上鎖的,對(duì)程序性能還是存在影響辫樱,下面介紹一種利用 Java 本身語(yǔ)法特性來(lái)實(shí)現(xiàn)的一種單例寫(xiě)法峭拘。
5. 靜態(tài)內(nèi)部類(lèi)實(shí)現(xiàn)單例
/**
* @author
* 靜態(tài)內(nèi)部類(lèi)實(shí)現(xiàn)單例
*/
public class LazyStaticInnerClassSingleton {
? ? private LazyStaticInnerClassSingleton() {
? ? }
? ? public static final LazyStaticInnerClassSingleton getInstance() {
? ? ? ? return LazyHolder.LAZY;
? ? }
? ? // 靜態(tài)內(nèi)部類(lèi),未被使用時(shí)狮暑,是不會(huì)被加載的
? ? private static class LazyHolder {
? ? ? ? private static final LazyStaticInnerClassSingleton LAZY = new LazyStaticInnerClassSingleton();
? ? }
}
用靜態(tài)內(nèi)部類(lèi)實(shí)現(xiàn)的單例本質(zhì)上是一種懶漢式鸡挠,因?yàn)樵趫?zhí)行g(shù)etInstance中的LazyHolder.LAZY語(yǔ)句之前,靜態(tài)內(nèi)部類(lèi)并不會(huì)被加載搬男。
這種方式既避免了餓漢式單例的內(nèi)存浪費(fèi)問(wèn)題拣展,又?jǐn)[脫了synchronized關(guān)鍵字的性能問(wèn)題,同時(shí)也不存在線程安全問(wèn)題缔逛。
到此為止备埃,我們介紹了 5 種單例寫(xiě)法(除去簡(jiǎn)單的懶漢式單例由于多線程問(wèn)題無(wú)法用于生產(chǎn)中姓惑,其實(shí)只有 4 種),我們發(fā)現(xiàn)上述單例模式本質(zhì)上都是將構(gòu)造方法私有化按脚,避免外部程序直接進(jìn)行實(shí)例化來(lái)達(dá)到單例的目的于毙。
那如果我們能夠想辦法獲取到類(lèi)的構(gòu)造方法,或者將創(chuàng)建好的對(duì)象寫(xiě)入磁盤(pán)乘寒,然后多次加載到內(nèi)存望众,是不是可以破壞上述所有的單例呢匪补?
答案是肯定的伞辛,下面我們用反射和序列化兩種方法親自毀滅我們一手搭建的單例。
6. 反射破壞單例
/**
* @author
* 利用反射破壞單例
*/
public class SingletonBrokenByReflect {
? ? public static void main(String[] args) {
? ? ? ? try {
? ? ? ? ? ? Class<?> clazz = LazyStaticInnerClassSingleton.class;
? ? ? ? ? ? //通過(guò)反射弧獲取類(lèi)的私有構(gòu)造方法
? ? ? ? ? ? Constructor c = clazz.getDeclaredConstructor(null);
? ? ? ? ? ? //強(qiáng)制訪問(wèn)
? ? ? ? ? ? c.setAccessible(true);
? ? ? ? ? ? Object obj1 = c.newInstance();
? ? ? ? ? ? Object obj2 = c.newInstance();
? ? ? ? ? ? //輸出false
? ? ? ? ? ? System.out.println(obj1 == obj2);
? ? ? ? } catch (Exception e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? }
? ? }
}
如此夯缺,我們便使用反射破壞了單例≡槭希現(xiàn)在我們以靜態(tài)內(nèi)部類(lèi)單例為例,解決這個(gè)問(wèn)題踊兜。
我們?cè)跇?gòu)造方法中添加一些限制竿滨,一旦檢測(cè)到對(duì)象已經(jīng)被實(shí)例化,但是構(gòu)造方法仍然被調(diào)用時(shí)直接拋出異常捏境。
/**
* @author
* 靜態(tài)內(nèi)部類(lèi)實(shí)現(xiàn)單例
*/
public class LazyStaticInnerClassSingleton {
? ? private LazyStaticInnerClassSingleton() {
? ? ? ? if (LazyHolder.LAZY != null) {
? ? ? ? ? ? throw new RuntimeException("實(shí)例被重復(fù)創(chuàng)建");
? ? ? ? }
? ? }
? ? public static final LazyStaticInnerClassSingleton getInstance() {
? ? ? ? return LazyHolder.LAZY;
? ? }
? ? // 靜態(tài)內(nèi)部類(lèi)于游,未被使用時(shí),是不會(huì)被加載的
? ? private static class LazyHolder {
? ? ? ? private static final LazyStaticInnerClassSingleton LAZY = new LazyStaticInnerClassSingleton();
? ? }
}
7. 序列化破壞單例
單例對(duì)象創(chuàng)建好之后垫言,有時(shí)需要將對(duì)象序列化然后寫(xiě)入磁盤(pán)贰剥,在需要時(shí)從磁盤(pán)中讀取對(duì)象并加載至內(nèi)存,反序列化后的對(duì)象會(huì)重新分配內(nèi)存筷频,如果序列化的目標(biāo)對(duì)象恰好是單例對(duì)象蚌成,就會(huì)破壞單例模式。
/**
* @author
* 可序列化的單例
*/
public class SeriableSingleton implements Serializable {
? ? //類(lèi)初始化的時(shí)候便進(jìn)行對(duì)象實(shí)例化
? ? private static final SeriableSingleton hungrySingleton = new SeriableSingleton();
? ? private SeriableSingleton() {
? ? }
? ? public static SeriableSingleton getInstance() {
? ? ? ? return hungrySingleton;
? ? }
}
/**
* @author
* 序列化破壞單例
*/
public class SingletonBrokenBySerializing {
? ? public static void main(String[] args) {
? ? ? ? SeriableSingleton s1 = SeriableSingleton.getInstance();
? ? ? ? SeriableSingleton s2 = null;
? ? ? ? FileOutputStream fos = null;
? ? ? ? try {
? ? ? ? ? ? File file;
? ? ? ? ? ? fos = new FileOutputStream("SeriableSingleton.obj");
? ? ? ? ? ? OutputStream out;
? ? ? ? ? ? ObjectOutputStream oos = new ObjectOutputStream(fos);
? ? ? ? ? ? oos.writeObject(s1);
? ? ? ? ? ? oos.flush();
? ? ? ? ? ? oos.close();
? ? ? ? ? ? fos.close();
? ? ? ? ? ? FileInputStream fis = new FileInputStream("SeriableSingleton.obj");
? ? ? ? ? ? ObjectInputStream ois = new ObjectInputStream(fis);
? ? ? ? ? ? s2 = (SeriableSingleton) ois.readObject();
? ? ? ? ? ? ois.close();
? ? ? ? ? ? fis.close();
? ? ? ? ? ? //輸出為false
? ? ? ? ? ? System.out.println(s1 == s2);
? ? ? ? } catch (Exception e) {
? ? ? ? }
? ? }
}
從運(yùn)行結(jié)果上看凛捏,反序列化和手動(dòng)創(chuàng)建出來(lái)的對(duì)象是不一致的担忧,違反了單例模式的初衷。
那到底如何保證在序列化的情況下也能夠?qū)崿F(xiàn)單例模式呢坯癣,其實(shí)很簡(jiǎn)單瓶盛,只需要增加一個(gè) readResolve 方法即可。
public class SeriableSingleton implements Serializable {
? ? //類(lèi)初始化的時(shí)候便進(jìn)行對(duì)象實(shí)例化
? ? private static final SeriableSingleton hungrySingleton = new SeriableSingleton();
? ? private SeriableSingleton() {
? ? }
? ? public static SeriableSingleton getInstance() {
? ? ? ? return hungrySingleton;
? ? }
? ? //只需要添加這一個(gè)函數(shù)即可
? ? private Object readResolve() {
? ? ? ? return hungrySingleton;
? ? }
}
實(shí)現(xiàn)的原理涉及到ObjectInputStream的源碼示罗,不屬于本文的研究重點(diǎn)惩猫,如果讀者需要,我可以另開(kāi)一篇來(lái)進(jìn)行講解鹉勒。
8. 注冊(cè)式單例模式
8.1 枚舉式單例模式
很多博客和文章的實(shí)現(xiàn)方式如下(文件名:EnumSingleObject.java)
/*
* @author
* 枚舉式單例1
*/
public class EnumSingleObject {
? ? private EnumSingleObject() {
? ? }
? ? enum SingletonEnum {
? ? ? ? INSTANCE;
? ? ? ? private EnumSingleObject instance;
? ? ? ? private SingletonEnum() {
? ? ? ? ? ? instance = new EnumSingleObject();
? ? ? ? }
? ? ? ? public EnumSingleObject getInstance() {
? ? ? ? ? ? return INSTANCE.instance;
? ? ? ? }
? ? }
? ? //對(duì)外暴露一個(gè)獲取EnumSingleObject對(duì)象的靜態(tài)方法
? ? public static EnumSingleObject getInstance() {
? ? ? ? return SingletonEnum.INSTANCE.getInstance();
? ? }
}
枚舉式的寫(xiě)法為什么可以實(shí)現(xiàn)我們的單例模式呢帆锋,我們首先使用javac EnumSingleObject.java生成EnumSingleObject.class文件,用反編譯工具Jad在.class 所在的目錄下執(zhí)行?jad EnumSingleObject.class命令禽额,得到EnumSingleObject.jad文件锯厢,代碼如下
static final class EnumSingleObject$SingletonEnum extends Enum {
? ? public static EnumSingleObject$SingletonEnum[] values() {
? ? ? ? return (EnumSingleObject$SingletonEnum[]) $VALUES.clone();
? ? }
? ? public static EnumSingleObject$SingletonEnum valueOf(String s) {
? ? ? ? return (EnumSingleObject$SingletonEnum) Enum.valueOf(com / chanmufeng / Singleton / registerSingleton / EnumSingleObject$SingletonEnum, s);
? ? }
? ? public EnumSingleObject getInstance() {
? ? ? ? return INSTANCE.instance;
? ? }
? ? public static final EnumSingleObject$SingletonEnum INSTANCE;
? ? private EnumSingleObject instance;
? ? private static final EnumSingleObject$SingletonEnum $VALUES[];
? ? // 該static代碼塊是枚舉寫(xiě)法能夠?qū)崿F(xiàn)單例模式的關(guān)鍵
? ? static {
? ? ? ? INSTANCE = new EnumSingleObject$SingletonEnum("INSTANCE", 0);
? ? ? ? $VALUES = (new EnumSingleObject$SingletonEnum[]{
? ? ? ? ? ? ? ? INSTANCE
? ? ? ? });
? ? }
? ? private EnumSingleObject$SingletonEnum(String s, int i) {
? ? ? ? super(s, i);
? ? ? ? instance = new EnumSingleObject();
? ? }
}
其實(shí)皮官,枚舉式單例在靜態(tài)代碼塊中就為INSTANCE進(jìn)行了賦值,是一種餓漢式單例模式的體現(xiàn)实辑,只不過(guò)這種餓漢式是 JDK 底層為我們做的操作捺氢,我們只是利用了 JDK 語(yǔ)法的特性罷了。
序列化能否破壞枚舉式單例
//測(cè)試序列化能否破壞
public static void main(String[] args) {
? ? ? ? EnumSingleObject s1 = EnumSingleObject.getInstance();
? ? ? ? EnumSingleObject s2 = null;
? ? ? ? FileOutputStream fos = null;
? ? ? ? try {
? ? ? ? ? ? fos = new FileOutputStream("SeriableSingleton.obj");
? ? ? ? ? ? ObjectOutputStream oos = new ObjectOutputStream(fos);
? ? ? ? ? ? oos.writeObject(s1);
? ? ? ? ? ? oos.flush();
? ? ? ? ? ? oos.close();
? ? ? ? ? ? fos.close();
? ? ? ? ? ? FileInputStream fis = new FileInputStream("SeriableSingleton.obj");
? ? ? ? ? ? ObjectInputStream ois = new ObjectInputStream(fis);
? ? ? ? ? ? s2 = (EnumSingleObject) ois.readObject();
? ? ? ? ? ? ois.close();
? ? ? ? ? ? fis.close();
? ? ? ? ? ? //輸出為false
? ? ? ? ? ? System.out.println(s1 == s2);
? ? ? ? } catch (Exception e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? }
? ? }
很遺憾剪撬,序列化依然會(huì)破壞枚舉式單例EnumSingleObject
What摄乒??残黑?不是說(shuō)枚舉式單例非常的優(yōu)雅嗎馍佑?連Effective Java?都推薦使用嗎?
別急梨水,接下來(lái)我們觀察另一種寫(xiě)法
/**
* @author
* 枚舉式單例2
*/
public enum EnumSingleObject2 {
? ? INSTANCE;
? ? private Object data;
? ? public Object getData() {
? ? ? ? return data;
? ? }
? ? public void setData(Object data) {
? ? ? ? this.data = data;
? ? }
? ? public static EnumSingleObject2 getInstance() {
? ? ? ? return INSTANCE;
? ? }
}
我們?cè)賮?lái)進(jìn)行序列化測(cè)試
public static void main(String[] args) {
? ? ? ? EnumSingleObject2 s1 = EnumSingleObject2.getInstance();
? ? ? ? s1.setData(new Object());
? ? ? ? EnumSingleObject2 s2 = null;
? ? ? ? FileOutputStream fos = null;
? ? ? ? try {
? ? ? ? ? ? fos = new FileOutputStream("SeriableSingleton.obj");
? ? ? ? ? ? ObjectOutputStream oos = new ObjectOutputStream(fos);
? ? ? ? ? ? oos.writeObject(s1);
? ? ? ? ? ? oos.flush();
? ? ? ? ? ? oos.close();
? ? ? ? ? ? fos.close();
? ? ? ? ? ? FileInputStream fis = new FileInputStream("SeriableSingleton.obj");
? ? ? ? ? ? ObjectInputStream ois = new ObjectInputStream(fis);
? ? ? ? ? ? s2 = (EnumSingleObject2) ois.readObject();
? ? ? ? ? ? ois.close();
? ? ? ? ? ? fis.close();
? ? ? ? ? ? //輸出為true
? ? ? ? ? ? System.out.println(s1 == s2);
? ? ? ? } catch (Exception e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? }
? ? }
打印結(jié)果為true拭荤,說(shuō)明枚舉式單例 2 的寫(xiě)法可以防止序列化破壞。
而很多文章和博客用的往往是第 1 種寫(xiě)法疫诽,下面我們解釋這兩種寫(xiě)法的區(qū)別.
我們進(jìn)入ObjectInputStream類(lèi)的readObject()方法
public final Object readObject()
? ? ? ? throws IOException, ClassNotFoundException
? ? {
? ? ? ...
? ? ? ? try {
? ? ? ? ? ? Object obj = readObject0(false);
? ? ? ? ? ...
? ? ? ? ? ? return obj;
? ? ? ? } finally {
? ? ? ? ? ...
? ? ? ? }
? ? }
在readObject()方法中又調(diào)用了readObject0()方法
private Object readObject0(boolean unshared) throws IOException {
? ? ...
? ? //枚舉式單例1的程序會(huì)進(jìn)入到這里
? ? case TC_CLASS:
? ? ? return readClass(unshared);
? ? ...
? ? //枚舉式單例2的程序會(huì)進(jìn)入到這里
? ? case TC_ENUM:
? ? ? return checkResolve(readEnum(unshared));
}
我們先看一下readEnum()方法
private Enum<?> readEnum(boolean unshared) throws IOException {
? ? ? ? ...
? ? ? ? String name = readString(false);
? ? ? ? Enum<?> result = null;
? ? ? ? Class<?> cl = desc.forClass();
? ? ? ? if (cl != null) {
? ? ? ? ? ? try {
? ? ? ? ? ? ? ? @SuppressWarnings("unchecked")
? ? ? ? ? ? ? ? //>耸馈!F嫱健雏亚!這里是重點(diǎn)
? ? ? ? ? ? ? ? Enum<?> en = Enum.valueOf((Class)cl, name);
? ? ? ? ? ? ? ? result = en;
? ? ? ? ? ? } catch (IllegalArgumentException ex) {
? ? ? ? ? ? ? ...
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? ...
? ? ? ? return result;
? ? }
到這里我們發(fā)現(xiàn),枚舉類(lèi)型其實(shí)通過(guò)類(lèi)名和類(lèi)對(duì)象找到唯一一個(gè)枚舉對(duì)象摩钙,因此罢低,枚舉對(duì)象不會(huì)被類(lèi)加載器加載多次。
而readClass()并無(wú)此功能腺律。
反射能否破壞枚舉式單例
public static void main(String[] args) {
? ? ? ? try {
? ? ? ? ? ? Class<?> clazz = EnumSingleObject2.class;
? ? ? ? ? ? //通過(guò)反射獲取類(lèi)的私有構(gòu)造方法
? ? ? ? ? ? Constructor c = clazz.getDeclaredConstructor(null);
? ? ? ? ? ? //強(qiáng)制訪問(wèn)
? ? ? ? ? ? c.setAccessible(true);
? ? ? ? ? ? Object obj1 = c.newInstance();
? ? ? ? ? ? Object obj2 = c.newInstance();
? ? ? ? ? ? //輸出false
? ? ? ? ? ? System.out.println(obj1 == obj2);
? ? ? ? } catch (Exception e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? }
? ? }
運(yùn)行結(jié)果如下
結(jié)果報(bào)了
java.lang.NoSuchMethodException異常奕短,原因是java.lang.Enum中沒(méi)有無(wú)參的構(gòu)造方法,我們查看java.lang.Enum的源碼匀钧,只有下面一個(gè)構(gòu)造函數(shù)
protected Enum(String name, int ordinal) {
? ? this.name = name;
? ? this.ordinal = ordinal;
}
我們改變一下反射構(gòu)建的方式
public static void main(String[] args) {
? ? ? ? try {
? ? ? ? ? ? Class<?> clazz = EnumSingleObject2.class;
? ? ? ? ? ? //通過(guò)反射獲取類(lèi)的私有構(gòu)造方法
//? ? ? ? ? ? Constructor c = clazz.getDeclaredConstructor(null);
? ? ? ? ? ? Constructor c = clazz.getDeclaredConstructor(String.class, int.class);
? ? ? ? ? ? //強(qiáng)制訪問(wèn)
? ? ? ? ? ? c.setAccessible(true);
? ? ? ? ? ? Object obj1 = c.newInstance();
? ? ? ? ? ? Object obj2 = c.newInstance();
? ? ? ? ? ? //輸出false
? ? ? ? ? ? System.out.println(obj1 == obj2);
? ? ? ? } catch (Exception e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? }
? ? }
運(yùn)行結(jié)果如下
public T newInstance(Object ... initargs)
? ? ? ? throws InstantiationException, IllegalAccessException,
? ? ? ? ? ? ? IllegalArgumentException, InvocationTargetException
? ? {
? ? ? ? ...
? ? ? ? if ((clazz.getModifiers() & Modifier.ENUM) != 0)
? ? ? ? ? ? throw new IllegalArgumentException("Cannot reflectively create enum objects");
? ? ? ? ConstructorAccessor ca = constructorAccessor;? // read volatile
? ? ? ? if (ca == null) {
? ? ? ? ? ? ca = acquireConstructorAccessor();
? ? ? ? }
? ? ? ? @SuppressWarnings("unchecked")
? ? ? ? T inst = (T) ca.newInstance(initargs);
? ? ? ? return inst;
? ? }
從源碼中可以看出翎碑,newInstance()方法中做了強(qiáng)制性的判斷,如果修飾符是Modifier.ENUM類(lèi)型之斯,則直接拋出異常日杈。
最后介紹一種注冊(cè)時(shí)單例的另一種寫(xiě)法:容器式單例
8.2 容器式單例模式
/**
* @author
* 容器式單例
*/
public class 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);
? ? ? ? ? ? }
? ? ? ? }
? ? }
}
容器式單例適合用于實(shí)例非常多的情況,Spring 中就使用了該種單例模式佑刷。
總結(jié)
單例模式可以保證內(nèi)存中任何情況下只有一個(gè)實(shí)例莉擒,是最簡(jiǎn)單的一種設(shè)計(jì)模式,實(shí)現(xiàn)起來(lái)也很簡(jiǎn)單瘫絮,但是實(shí)現(xiàn)方式比較多涨冀,涉及到的小細(xì)節(jié)也比較多,在面試中是一個(gè)高頻面試點(diǎn)麦萤。