單例模式不難拴竹,一篇搞懂

?單例模式

有些對(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)麦萤。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末鹿鳖,一起剝皮案震驚了整個(gè)濱河市扁眯,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌翅帜,老刑警劉巖姻檀,帶你破解...
    沈念sama閱讀 218,607評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異涝滴,居然都是意外死亡绣版,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)歼疮,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)杂抽,“玉大人,你說(shuō)我怎么就攤上這事腋妙∧梗” “怎么了讯榕?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,960評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵骤素,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我愚屁,道長(zhǎng)济竹,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,750評(píng)論 1 294
  • 正文 為了忘掉前任霎槐,我火速辦了婚禮送浊,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘丘跌。我一直安慰自己袭景,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,764評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布闭树。 她就那樣靜靜地躺著耸棒,像睡著了一般。 火紅的嫁衣襯著肌膚如雪报辱。 梳的紋絲不亂的頭發(fā)上与殃,一...
    開(kāi)封第一講書(shū)人閱讀 51,604評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音碍现,去河邊找鬼幅疼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛昼接,可吹牛的內(nèi)容都是我干的爽篷。 我是一名探鬼主播,決...
    沈念sama閱讀 40,347評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼慢睡,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼逐工!你這毒婦竟也來(lái)了膨疏?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,253評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤钻弄,失蹤者是張志新(化名)和其女友劉穎佃却,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體窘俺,經(jīng)...
    沈念sama閱讀 45,702評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡饲帅,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,893評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了瘤泪。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片灶泵。...
    茶點(diǎn)故事閱讀 40,015評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖对途,靈堂內(nèi)的尸體忽然破棺而出赦邻,到底是詐尸還是另有隱情,我是刑警寧澤实檀,帶...
    沈念sama閱讀 35,734評(píng)論 5 346
  • 正文 年R本政府宣布惶洲,位于F島的核電站,受9級(jí)特大地震影響膳犹,放射性物質(zhì)發(fā)生泄漏恬吕。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,352評(píng)論 3 330
  • 文/蒙蒙 一须床、第九天 我趴在偏房一處隱蔽的房頂上張望铐料。 院中可真熱鬧,春花似錦豺旬、人聲如沸钠惩。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,934評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)篓跛。三九已至,卻和暖如春耘分,著一層夾襖步出監(jiān)牢的瞬間举塔,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,052評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工求泰, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留央渣,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,216評(píng)論 3 371
  • 正文 我出身青樓渴频,卻偏偏與公主長(zhǎng)得像芽丹,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子卜朗,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,969評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 今天青石的票圈出鏡率最高的泳猬,莫過(guò)于張藝謀的新片終于定檔了批钠。 一張滿(mǎn)溢著水墨風(fēng)的海報(bào)一次次的出現(xiàn)在票圈里,也就是老謀...
    青石電影閱讀 10,334評(píng)論 1 2
  • 一得封、jQuery簡(jiǎn)介 JQ是JS的一個(gè)優(yōu)秀的庫(kù)埋心,大型開(kāi)發(fā)必備。在此忙上,我想說(shuō)的是拷呆,JQ里面很多函數(shù)使用和JS類(lèi)似,所...
    Welkin_qing閱讀 12,339評(píng)論 1 6
  • 字符串 1.什么是字符串 使用單引號(hào)或者雙引號(hào)括起來(lái)的字符集就是字符串疫粥。 引號(hào)中單獨(dú)的符號(hào)茬斧、數(shù)字、字母等叫字符手形。 ...
    mango_2e17閱讀 7,510評(píng)論 1 7
  • 一場(chǎng)說(shuō)走就走的旅行啥供。 簡(jiǎn)單地整理幾件必備旅行用品,7.18日早上8.00準(zhǔn)時(shí)出發(fā)了库糠,計(jì)劃一路南下然后繞西南一圈回恩...
    悠游魚(yú)閱讀 3,472評(píng)論 3 6
  • 原來(lái)看過(guò)一段話(huà),第一厲害的人有能力沒(méi)脾氣涮毫,第二厲害的人有能力有脾氣瞬欧,最差的是沒(méi)能力有脾氣的人。 以我最...
    涼風(fēng)豆豆閱讀 1,546評(píng)論 2 0