java面試常見(jiàn)模式問(wèn)題---單例模式
單例模式使⽤場(chǎng)景:
業(yè)務(wù)系統(tǒng)全局只需要⼀個(gè)對(duì)象實(shí)例,⽐如發(fā)號(hào)器、 redis 連接對(duì)象等。 Spring IOC容器中的 Bean 默認(rèn)就是單例。 Spring Boot 中的 Controller、Service、Dao 層中通過(guò) @Autowire的依賴(lài)注⼊對(duì)象默認(rèn)都是單例的。單例模式分類(lèi):
懶漢:就是所謂的懶加載,延遲創(chuàng)建對(duì)象,需要用的時(shí)候再創(chuàng)建對(duì)象。 餓漢:與懶漢相反,提前創(chuàng)建對(duì)象。 單例模式實(shí)現(xiàn)步驟: 私有化構(gòu)造函數(shù)。 提供獲取單例的方法。2、單例模式——懶漢式單例模式——懶漢式有以下⼏種實(shí)現(xiàn)⽅式:/** * @Auther: csp1999 * @Date: 2020/11/06/20:36 * @Description: 單例設(shè)計(jì)模式-懶漢式 */public class SingletonLazy { // 當(dāng)需要用到該實(shí)例的時(shí)候再創(chuàng)建實(shí)例對(duì)象 private static SingletonLazy instance; /** * 構(gòu)造函數(shù)私有化 * 不能通過(guò) new SingletonLazy() 的方式創(chuàng)建實(shí)例 * * 當(dāng)需要用到該實(shí)例的時(shí)候在加載 * 只能通過(guò) SingletonLazy.getInstance() 這種方式獲取實(shí)例 */ private SingletonLazy() { } /** * 單例對(duì)象的方法 */ public void process() {System.out.println('方法實(shí)例化成功!'); } /** * 方式一: * <p> * 對(duì)外暴露一個(gè)方法獲取該類(lèi)的對(duì)象 * <p> * 缺點(diǎn):線程不安全,多線程下存在安全問(wèn)題 * * @return */ public static SingletonLazy getInstance() {if (instance == null) {// 實(shí)例為null時(shí)候才創(chuàng)建 /** * 線程安全問(wèn)題: * 當(dāng)某一時(shí)刻,兩個(gè)或多個(gè)線程同時(shí)判斷到instance == null成立的時(shí)候 * 這些線程同時(shí)進(jìn)入該if判斷內(nèi)部執(zhí)行實(shí)例化 * 則會(huì)新建出不止一個(gè)SingletonLazy實(shí)例 */ instance = new SingletonLazy();// 當(dāng)需要的時(shí)候再進(jìn)行實(shí)例化對(duì)象}return instance; } /** * 方式二: * 通過(guò)加synchronized鎖 保證線程安全 * * 采用synchronized 對(duì)方法加鎖有很大的性能開(kāi)銷(xiāo) * 因?yàn)楫?dāng)getInstance2()內(nèi)部邏輯比較復(fù)雜的時(shí)候,在高并發(fā)條件下 * 沒(méi)獲取到加鎖方法執(zhí)行權(quán)的線程,都得等到這個(gè)方法內(nèi)的復(fù)雜邏輯執(zhí)行完后才能執(zhí)行,等待浪費(fèi)時(shí)間,效率比較低 * * @return */ public static synchronized SingletonLazy getInstance2() {if (instance == null) {// 實(shí)例為null時(shí)候才創(chuàng)建 // 方法上加synchronized鎖后可以保證線程安全 instance = new SingletonLazy();// 當(dāng)需要的時(shí)候再進(jìn)行實(shí)例化對(duì)象}return instance; } /** * 方式三: * 在getInstance3()方法內(nèi),針對(duì)局部需要加鎖的代碼塊加鎖,而不是給整個(gè)方法加鎖 * * 也存在缺陷: * @return */ public static SingletonLazy getInstance3() {if (instance == null) {// 實(shí)例為null時(shí)候才創(chuàng)建 // 局部加鎖后可以保證線程安全,效率較高 // 缺陷:假設(shè)線程A和線程B synchronized (SingletonLazy.class){// 當(dāng)線程A獲得鎖的執(zhí)行權(quán)的時(shí)候B等待 A執(zhí)行new SingletonLazy();實(shí)例化// 當(dāng)A線程執(zhí)行完畢后,B再獲得執(zhí)行權(quán),這時(shí)候還是可以實(shí)例化該對(duì)象instance = new SingletonLazy();// 當(dāng)需要的時(shí)候再進(jìn)行實(shí)例化對(duì)象 }}return instance; }}單例模式:懶漢實(shí)現(xiàn) + 雙重檢查鎖定 + 內(nèi)存模型
對(duì)于上面方式三存在的缺陷,我們可以使用雙重檢查鎖定的方式對(duì)其進(jìn)行改進(jìn):
/** * 方式三改進(jìn)版本: * 在getInstance3()方法內(nèi),針對(duì)局部需要加鎖的代碼塊加鎖,而不是給整個(gè)方法加鎖 * * DCL 雙重檢查鎖定 (Double-Checked-Locking) 在多線程情況下保持高性能 * * 這是否安全? instance = new SingletonLazy(); 并不是原子性操作 * jvm中 instance實(shí)例化內(nèi)存模型流程如下: * 1.分配空間給對(duì)象 * 2.在空間內(nèi)創(chuàng)建對(duì)象 * 3.將對(duì)象賦值給instance引用 * * 假如出現(xiàn)如下順序錯(cuò)亂的情況: * 線程的執(zhí)行順序?yàn)椋? -> 3 -> 2, 那么這時(shí)候會(huì)把值寫(xiě)回主內(nèi)存 * 則,其他線程就會(huì)讀取到instance的最新值,但是這個(gè)是不完全的對(duì)象 * (指令重排現(xiàn)象) * * @return */public static SingletonLazy getInstance3plus() { if (instance == null) {// 實(shí)例為null時(shí)候才創(chuàng)建// 局部加鎖后可以保證線程安全,效率較高// 假設(shè)線程A和線程B synchronized (SingletonLazy.class){// 第一重檢查 // 當(dāng)線程A獲得鎖的執(zhí)行權(quán)的時(shí)候B等待 A執(zhí)行new SingletonLazy();實(shí)例化 // 當(dāng)A線程執(zhí)行完畢后,B再獲得執(zhí)行權(quán),這時(shí)候再判斷instance == null是否成立 // 如果不成立,B線程無(wú)法 實(shí)例化SingletonLazy if (instance == null){// 第二重檢查instance = new SingletonLazy();// 當(dāng)需要的時(shí)候再進(jìn)行實(shí)例化對(duì)象 }} } return instance;}
再次升級(jí)方式三,來(lái)解決內(nèi)存模型中的指令重排問(wèn)題:
// 添加volatile 關(guān)鍵字,禁止實(shí)例化對(duì)象時(shí),內(nèi)存模型中出現(xiàn)指令重排現(xiàn)象private static volatile SingletonLazy instance;/** * 方式三再次升級(jí)版本: * 在getInstance3()方法內(nèi),針對(duì)局部需要加鎖的代碼塊加鎖,而不是給整個(gè)方法加鎖 * * DCL 雙重檢查鎖定 (Double-Checked-Locking) 在多線程情況下保持高性能 * * 解決指令重排問(wèn)題——禁止指令重排 * @return */public static SingletonLazy getInstance3plusplus() { if (instance == null) {// 實(shí)例為null時(shí)候才創(chuàng)建// 局部加鎖后可以保證線程安全,效率較高// 假設(shè)線程A和線程Bsynchronized (SingletonLazy.class){// 第一重檢查 // 當(dāng)線程A獲得鎖的執(zhí)行權(quán)的時(shí)候B等待 A執(zhí)行new SingletonLazy();實(shí)例化 // 當(dāng)A線程執(zhí)行完畢后,B再獲得執(zhí)行權(quán),這時(shí)候再判斷instance == null是否成立 // 如果不成立,B線程無(wú)法 實(shí)例化SingletonLazy if (instance == null){// 第二重檢查instance = new SingletonLazy();// 當(dāng)需要的時(shí)候再進(jìn)行實(shí)例化對(duì)象 }} } return instance;}
單例模式——懶漢式調(diào)用:
@Testpublic void testSingletonLazy(){ SingletonLazy.getInstance().process();}3、單例模式——餓漢式
/** * @Auther: csp1999 * @Date: 2020/11/06/21:39 * @Description: 單例設(shè)計(jì)模式-餓漢式 */public class SingletonHungry { // 當(dāng)類(lèi)加載的時(shí)候就直接實(shí)例化對(duì)象 private static SingletonHungry instance = new SingletonHungry(); private SingletonHungry(){} /** * 單例對(duì)象的方法 */ public void process() {System.out.println('方法實(shí)例化成功!'); } public static SingletonHungry getInstance(){return instance;// 當(dāng)類(lèi)加載的時(shí)候就直接實(shí)例化對(duì)象 }}單例模式——餓漢式調(diào)用:
@Testpublic void testSingletonHungry(){ SingletonHungry.getInstance().process();}
餓漢式單例模式,當(dāng)類(lèi)加載的時(shí)候就直接實(shí)例化對(duì)象,因此不需要考慮線程安全問(wèn)題。
優(yōu)點(diǎn):實(shí)現(xiàn)簡(jiǎn)單,不需要考慮線程安全問(wèn)題。 缺點(diǎn):不管有沒(méi)有使用該對(duì)象實(shí)例,instance對(duì)象一直占用著這段內(nèi)存。懶漢與餓漢式如何選擇?
如果對(duì)象內(nèi)存占用不大,且創(chuàng)建不復(fù)雜,直接使用餓漢的方式即可。 其他情況均采用懶漢方式(優(yōu)選)。總結(jié)文章會(huì)不定時(shí)更新,有時(shí)候一天多更新幾篇,如果幫助您復(fù)習(xí)鞏固了知識(shí)點(diǎn),還請(qǐng)支持一下,后續(xù)會(huì)億點(diǎn)點(diǎn)的更新!希望大家多多關(guān)注好吧啦網(wǎng)的其他內(nèi)容!
相關(guān)文章:
1. 利用promise及參數(shù)解構(gòu)封裝ajax請(qǐng)求的方法2. Nginx+php配置文件及原理解析3. windows服務(wù)器使用IIS時(shí)thinkphp搜索中文無(wú)效問(wèn)題4. .NET中l(wèi)ambda表達(dá)式合并問(wèn)題及解決方法5. JSP數(shù)據(jù)交互實(shí)現(xiàn)過(guò)程解析6. 淺談python出錯(cuò)時(shí)traceback的解讀7. python matplotlib:plt.scatter() 大小和顏色參數(shù)詳解8. Ajax實(shí)現(xiàn)表格中信息不刷新頁(yè)面進(jìn)行更新數(shù)據(jù)9. Python importlib動(dòng)態(tài)導(dǎo)入模塊實(shí)現(xiàn)代碼10. ASP 信息提示函數(shù)并作返回或者轉(zhuǎn)向
