摘要:防止指令重排序防止時(shí)指令重排序?qū)е缕渌€程獲取到未初始化完的對(duì)象。枚舉類默認(rèn)枚舉實(shí)例的創(chuàng)建是線程安全的,所以不需要擔(dān)心線程安全的問題。
單例模式是23種GOF模式中最簡單,也是最經(jīng)常出現(xiàn)的一種設(shè)計(jì)模式,也是面試官最常愛考的一種模式,為什么呢?
因?yàn)閱卫J阶銐蚝唵危帉懸粋€(gè)單例模式代碼幾分鐘就能搞定,所以設(shè)計(jì)模式中面試官通常會(huì)選取單例模式作為出題。
下面把單例模式分幾個(gè)點(diǎn),分別說說哪些地方面試官能考你?
通常面試官會(huì)很籠統(tǒng)的問你,什么是單例模式?單例模式用來解決了什么痛點(diǎn)?沒有單例模式我們會(huì)怎么辦?單例模式他有什么缺點(diǎn)嗎?
單例模式是最簡單的設(shè)計(jì)模式之一,屬于創(chuàng)建型模式,它提供了一種創(chuàng)建對(duì)象的方式,確保只有單個(gè)對(duì)象被創(chuàng)建。這個(gè)設(shè)計(jì)模式主要目的是想在整個(gè)系統(tǒng)中只能出現(xiàn)類的一個(gè)實(shí)例,即一個(gè)類只有一個(gè)對(duì)象。
單例模式的解決的痛點(diǎn)就是節(jié)約資源,節(jié)省時(shí)間從兩個(gè)方面看:
1.由于頻繁使用的對(duì)象,可以省略創(chuàng)建對(duì)象所花費(fèi)的時(shí)間,這對(duì)于那些重量級(jí)的對(duì)象而言,是很重要的.
2.因?yàn)椴恍枰l繁創(chuàng)建對(duì)象,我們的GC壓力也減輕了,而在GC中會(huì)有STW(stop the world),從這一方面也節(jié)約了GC的時(shí)間
單例模式的缺點(diǎn):簡單的單例模式設(shè)計(jì)開發(fā)都比較簡單,但是復(fù)雜的單例模式需要考慮線程安全等并發(fā)問題,引入了部分復(fù)雜度。
擴(kuò)展:從你的回答中能進(jìn)行哪些擴(kuò)展呢?我們談到了GC,有可能這時(shí)候就會(huì)問你GC,STW等知識(shí)。談缺點(diǎn)的時(shí)候談到了復(fù)雜的單例模式,
這個(gè)時(shí)候可能會(huì)問你讓你設(shè)計(jì)一個(gè)優(yōu)秀的單例模式你會(huì)怎么設(shè)計(jì),會(huì)怎么實(shí)現(xiàn)?
通常這里面試官會(huì)問你單例模式怎么設(shè)計(jì),需要看重哪些方面?一般來說單例模式有哪些實(shí)現(xiàn)方式?
設(shè)計(jì)單例模式的時(shí)候一般需要考慮幾種因素:
-線程安全
-延遲加載
-代碼安全:如防止序列化攻擊,防止反射攻擊(防止反射進(jìn)行私有方法調(diào)用)
-性能因素
一般來說,我們?nèi)ゾW(wǎng)上百度去搜大概有7,8種實(shí)現(xiàn),,下面列舉一下需要重點(diǎn)知道的
餓漢,懶漢(線程安全,線程非安全),雙重檢查(DCL)(重點(diǎn)),內(nèi)部類,以及枚舉(重點(diǎn)),
擴(kuò)展:我們上面說到了各個(gè)模式的實(shí)現(xiàn),這個(gè)時(shí)候很有可能會(huì)叫你手寫各個(gè)模式的代碼。當(dāng)然也有可能會(huì)問你線程安全,代碼安全等知識(shí)。
餓漢模式餓漢模式的代碼如下:
public class Singleton { private static Singleton instance = new Singleton(); private Singleton(){} public static Singleton getInstance(){ return instance; } }
餓漢模式代碼比較簡單,對(duì)象在類中被定義為private static,通過getInstance(),通過java的classLoader機(jī)制保證了單例對(duì)象唯一。
擴(kuò)展:
有可能會(huì)問instance什么時(shí)候被初始化?雙重檢查DCLSingleton類被加載的時(shí)候就會(huì)被初始化,java虛擬機(jī)規(guī)范雖然沒有強(qiáng)制性約束在什么時(shí)候開始類加載過程,但是對(duì)于類的初始化,虛擬機(jī)規(guī)范則嚴(yán)格規(guī)定了有且只有四種情況必須立即對(duì)類進(jìn)行初始化,遇到new、getStatic、putStatic或invokeStatic這4條字節(jié)碼指令時(shí),如果類沒有進(jìn)行過初始化,則需要先觸發(fā)其初始化。 生成這4條指令最常見的java代碼場景是:1)使用new關(guān)鍵字實(shí)例化對(duì)象2)讀取一個(gè)類的靜態(tài)字段(被final修飾、已在編譯期把結(jié)果放在常量池的靜態(tài)字段除外)3)設(shè)置一個(gè)類的靜態(tài)字段(被final修飾、已在編譯期把結(jié)果放在常量池的靜態(tài)字段除外)4)調(diào)用一個(gè)類的靜態(tài)方法
class的生命周期?
class的生命周期一般來說會(huì)經(jīng)歷加載、連接、初始化、使用、和卸載五個(gè)階段
class的加載機(jī)制
這里可以聊下classloader的雙親委派模型。
public class Singleton { private volatile static Singleton singleton; private Singleton (){} public static Singleton getSingleton() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } }
synchronized同步塊里面能夠保證只創(chuàng)建一個(gè)對(duì)象。但是通過在synchronized的外面增加一層判斷,就可以在對(duì)象一經(jīng)創(chuàng)建以后,不再進(jìn)入synchronized同步塊。這種方案不僅減小了鎖的粒度,保證了線程安全,性能方面也得到了大幅提升。
同時(shí)這里要注意一定要說volatile,這個(gè)很關(guān)鍵,volatile一般用于多線程的可見性,但是這里是用來防止指令重排序的。
擴(kuò)展:
為什么需要volatile?volatile有什么用?首先要回答可見性,這個(gè)是毋庸質(zhì)疑的,然后可能又會(huì)考到j(luò)ava內(nèi)存模型。
防止指令重排序: 防止new Singleton時(shí)指令重排序?qū)е缕渌€程獲取到未初始化完的對(duì)象。instance = new Singleton()這句,這并非是一個(gè)原子操作,事實(shí)上在 JVM 中這句話大概做了下面 3 件事情。1.給 instance 分配內(nèi)存2.調(diào)用 Singleton 的構(gòu)造函數(shù)來初始化成員變量3.將instance對(duì)象指向分配的內(nèi)存空間(執(zhí)行完這步 instance 就為非 null 了)
但是在 JVM 的即時(shí)編譯器中存在指令重排序的優(yōu)化。也就是說上面的第二步和第三步的順序是不能保證的,最終的執(zhí)行順序可能是 1-2-3 也可能是 1-3-2。如果是后者,則在 3 執(zhí)行完畢、2 未執(zhí)行之前,被線程二搶占了,這時(shí) instance 已經(jīng)是非 null 了(但卻沒有初始化),所以線程二會(huì)直接返回 instance,然后使用,然后報(bào)錯(cuò)。
順便也可以說下volatie原理用內(nèi)存屏障
講講synchronized和volatile的區(qū)別這里可以從synchroized能保證原子性,volatile不能保證說起,以及講下synchroized是重量級(jí)鎖,甚至可以所以下他和Lock的區(qū)別等等。
線程安全一般怎么實(shí)現(xiàn)的?互斥同步。如lock,synchroized
非阻塞同步。如cas。
不同步。如threadLocal,局部變量。
枚舉類public enum Singleton{ INSTANCE; }
默認(rèn)枚舉實(shí)例的創(chuàng)建是線程安全的,所以不需要擔(dān)心線程安全的問題。同時(shí)他也是《Effective Java》中推薦的模式。最后通過枚舉類,他能自動(dòng)避免序列化/反序列化攻擊,以及反射攻擊(枚舉類不能通過反射生成)。
總結(jié)單例模式雖然看起來簡單,但是設(shè)計(jì)的Java基礎(chǔ)知識(shí)非常多,如static修飾符、synchronized修飾符、volatile修飾符、enum等。這里的每一個(gè)知識(shí)點(diǎn)都可以變成面試官下手的考點(diǎn),而單例只是作為一個(gè)引子,考到最后看你到底掌握了多少??茨愕膹V度和深度到底是怎么樣的。
如果這篇文章對(duì)你有幫助,想要了解更多?;蛘呦胍?對(duì)1的交流。請(qǐng)關(guān)注下方公眾號(hào)吧。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/76415.html
摘要:面試時(shí)經(jīng)常會(huì)問到關(guān)于單例設(shè)計(jì)模式,因?yàn)樗芸疾斓闹R(shí)點(diǎn)較多且在開發(fā)中經(jīng)常用到。那我就來說一說我對(duì)于單例設(shè)計(jì)模式的一些淺見。還有另一種實(shí)現(xiàn)方法稱為懶漢式。但以上代碼會(huì)出現(xiàn)線程安全問題。 Java面試時(shí)經(jīng)常會(huì)問到關(guān)于單例設(shè)計(jì)模式,因?yàn)樗芸疾斓闹R(shí)點(diǎn)較多且在開發(fā)中經(jīng)常用到。那我就來說一說我對(duì)于單例設(shè)計(jì)模式的一些淺見。首先,在Java中,什么是單例呢?就是保證類在內(nèi)存中只有一個(gè)對(duì)象。那么問題...
摘要:面試官要不你來手寫下單例模式唄候選者單例模式一般會(huì)有好幾種寫法候選者餓漢式簡單懶漢式在方法聲明時(shí)加鎖雙重檢驗(yàn)加鎖進(jìn)階懶漢式靜態(tài)內(nèi)部類優(yōu)雅懶漢式枚舉候選者所謂餓漢式指的就是還沒被用到,就直接初始化了對(duì)象。面試官:我看你的簡歷寫著熟悉常見的設(shè)計(jì)模式,要不你來簡單聊聊你熟悉哪幾個(gè)吧?候選者:常見的工廠模式、代理模式、模板方法模式、責(zé)任鏈模式、單例模式、包裝設(shè)計(jì)模式、策略模式等都是有所了解的候選者:...
摘要:到底什么是閉包這個(gè)問題在面試是時(shí)候經(jīng)常都會(huì)被問,很多小白一聽就懵逼了,不知道如何回答好。上面這么說閉包是一種特殊的對(duì)象。閉包的注意事項(xiàng)通常,函數(shù)的作用域及其所有變量都會(huì)在函數(shù)執(zhí)行結(jié)束后被銷毀。從而使用閉包模塊化代碼,減少全局變量的污染。 閉包,有人說它是一種設(shè)計(jì)理念,有人說所有的函數(shù)都是閉包。到底什么是閉包?這個(gè)問題在面試是時(shí)候經(jīng)常都會(huì)被問,很多小白一聽就懵逼了,不知道如何回答好。這個(gè)...
摘要:顯而易見的,當(dāng)這個(gè)是的時(shí),就不存在內(nèi)存泄漏的問題。這個(gè)我在第一期自定義如何有效保證內(nèi)存泄漏問題已經(jīng)說得很明白了。 零零碎碎的東西總是記不長久,僅僅學(xué)習(xí)別人的文章也只是他人咀嚼后留下的殘?jiān)?。無意中發(fā)現(xiàn)了這個(gè)每日一道面試題,想了想如果只是簡單地去思考,那么不僅會(huì)收效甚微,甚至難一點(diǎn)的題目自己可能都懶得去想,堅(jiān)持不下來。所以不如把每一次的思考、理解以及別人的見解記錄下來。不僅加深自己的理解,更要激...
閱讀 4055·2021-09-30 09:59
閱讀 2546·2021-09-13 10:34
閱讀 654·2019-08-30 12:58
閱讀 1581·2019-08-29 18:42
閱讀 2271·2019-08-26 13:44
閱讀 2998·2019-08-23 18:12
閱讀 3393·2019-08-23 15:10
閱讀 1703·2019-08-23 14:37