摘要:如果需要防范這種攻擊,請(qǐng)修改構(gòu)造函數(shù),使其在被要求創(chuàng)建第二個(gè)實(shí)例時(shí)拋出異常。單例模式與單一職責(zé)原則有沖突。源碼地址參考文獻(xiàn)設(shè)計(jì)模式之禪
定義
單例模式是一個(gè)比較"簡(jiǎn)單"的模式,其定義如下:
保證一個(gè)類僅有一個(gè)實(shí)例,并提供一個(gè)訪問(wèn)它的全局訪問(wèn)點(diǎn)。
或者
Ensure a class has only one instance, and provide a global point of access to it.確保某一個(gè)類只有一個(gè)實(shí)例,而且自行實(shí)例化并向整個(gè)系統(tǒng)提供這個(gè)實(shí)例。
請(qǐng)注意"簡(jiǎn)單"二字的雙引號(hào),說(shuō)它簡(jiǎn)單它也簡(jiǎn)單,但是要想用好、用對(duì)其實(shí)并不那么簡(jiǎn)單,為什么這么說(shuō)?
首先,單例模式的定義比較好理解,應(yīng)用場(chǎng)景明確,實(shí)現(xiàn)思路比較簡(jiǎn)單;
其次,單例模式其實(shí)要考慮的因素很多,諸如延遲加載、線程安全以及破壞單例的情況等等。也正是這些因素導(dǎo)致單例模式的實(shí)現(xiàn)方式多樣,且各有利弊
特點(diǎn)單例類只能有一個(gè)實(shí)例;
單例類必須自己創(chuàng)建自己的唯一實(shí)例;
單例類必須給所有其他對(duì)象提供這一實(shí)例。
基本步驟私有的靜態(tài)成員變量:在本類中創(chuàng)建唯一實(shí)例,使用靜態(tài)成員變量保存;為保證安全性,私有化這個(gè)成員變量
私有的構(gòu)造方法:避免其他類可以直接創(chuàng)建單例類的對(duì)象
公有的靜態(tài)方法:供其他類獲取本類的唯一實(shí)例
考慮的因素延遲加載
線程安全
破壞單例的情況
序列化
如果Singleton類是可序列化的,僅僅在生聲明中加上implements Serializable是不夠的。為了維護(hù)并保證Singleton,必須聲明所有實(shí)例域都是瞬時(shí)(transient)的,并且提供一個(gè)readResolve方法。否則,每次反序列化一個(gè)序列化的實(shí)例時(shí),都會(huì)創(chuàng)建一個(gè)新的對(duì)象。
反射
授權(quán)的客戶端可以通過(guò)反射來(lái)調(diào)用私有構(gòu)造方法,借助于AccessibleObject.setAccessible方法即可做到 。如果需要防范這種攻擊,請(qǐng)修改構(gòu)造函數(shù),使其在被要求創(chuàng)建第二個(gè)實(shí)例時(shí)拋出異常。
private Singleton() { System.err.println("Singleton Constructor is invoked!"); if (singleton != null) { System.err.println("實(shí)例已存在,無(wú)法初始化!"); throw new UnsupportedOperationException("實(shí)例已存在,無(wú)法初始化!"); } } }
對(duì)象復(fù)制
在Java中,對(duì)象默認(rèn)是不可以被復(fù)制的,若實(shí)現(xiàn)了Cloneable接口,并實(shí)現(xiàn)了clone方法,則可以直接通過(guò)對(duì)象復(fù)制方式創(chuàng)建一個(gè)新對(duì)象,對(duì)象復(fù)制是不用調(diào)用類的構(gòu)造函數(shù),因此即使是私有的構(gòu)造函數(shù),對(duì)象仍然可以被復(fù)制。在一般情況下,類復(fù)制的情況不需要考慮,很少會(huì)出現(xiàn)一個(gè)單例類會(huì)主動(dòng)要求被復(fù)制的情況,解決該問(wèn)題的最好方法就是單例類不要實(shí)現(xiàn)Cloneable接口。
類加載器
如果單例由不同的類裝載器裝入,那便有可能存在多個(gè)單例類的實(shí)例。
實(shí)現(xiàn)方式 1、懶漢式public class Singleton { private static Singleton singleton; private Singleton() { } public static Singleton getInstance() { if (singleton == null) { singleton = new Singleton(); } return singleton; } }
優(yōu)點(diǎn):延遲加載
缺點(diǎn):線程不安全,多線程環(huán)境下有可能產(chǎn)生多個(gè)實(shí)例
為解決懶漢式"線程安全問(wèn)題",可以將getInstance()設(shè)置為同步方法,于是就有了第二種實(shí)現(xiàn)方式:
public class Singleton { private static Singleton singleton; private Singleton() { } public static synchronized Singleton getInstance() { if (singleton == null) { singleton = new Singleton(); } return singleton; } }
優(yōu)點(diǎn):延遲加載,并且線程安全
缺點(diǎn):效率很低,99%的情況下其實(shí)是不需要同步的
2、餓漢式public class Singleton { private static Singleton singleton = new Singleton(); private Singleton() { } public static Singleton getInstance() { return singleton; } }
優(yōu)點(diǎn):線程安全,實(shí)現(xiàn)簡(jiǎn)單
缺點(diǎn):沒有延遲加載,類加載的時(shí)候即完成初始化,可能在一定程度上造成內(nèi)存空間的浪費(fèi)
如果不是特別需要延遲加載的場(chǎng)景,可以優(yōu)先考慮餓漢式
3、雙重檢查鎖public class Singleton { private static volatile Singleton singleton; private Singleton() { } public static Singleton getInstance() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } }
優(yōu)點(diǎn):延遲加載,線程安全,并且效率也很不錯(cuò)
缺點(diǎn):實(shí)現(xiàn)相對(duì)復(fù)雜一點(diǎn),JDK1.5以后才支持volatile
說(shuō)明
將同步方法改為同步代碼塊
第一個(gè)判空是為了解決效率問(wèn)題,不需要每次都進(jìn)入同步代碼塊
synchronized (Singleton.class)是為了解決線程安全問(wèn)題
第二個(gè)判空是避免產(chǎn)生多個(gè)實(shí)例
volatile修飾符是禁止指令重排序
這里針對(duì)volatile多說(shuō)兩句,很多書上和網(wǎng)上的雙重檢查鎖實(shí)例都沒有加volatile,事實(shí)上這是不正確的
首先,volatile的兩層含義:
內(nèi)存可見性
禁止指令重排
這里我們用到的主要是第二個(gè)語(yǔ)義。那么什么是指令重排序呢,就是指編譯器和處理器為了優(yōu)化程序性能而對(duì)指令序列進(jìn)行排序的一種手段。簡(jiǎn)單理解,就是編譯器對(duì)我們的代碼進(jìn)行了優(yōu)化,在實(shí)際執(zhí)行指令的的時(shí)候可能與我們編寫的順序不同,只保證程序執(zhí)行結(jié)果與源代碼相同,卻不保證實(shí)際指令的順序與源代碼相同。
singleton = new Singleton();
這段代碼在jvm執(zhí)行時(shí)實(shí)際分為三步:
在堆內(nèi)存開辟一塊內(nèi)存空間;
在堆內(nèi)存實(shí)例化Singleton
把對(duì)象(singleton)指向堆內(nèi)存空間
由于"指令重排"的優(yōu)化,很可能執(zhí)行步驟為1-3-2,即:對(duì)象并沒有實(shí)例化完成但引用已經(jīng)是非空了,也就是在第二處判空的地方為false,直接返回singleton——一個(gè)未完成實(shí)例化的對(duì)象引用。
這里涉及到Java內(nèi)存模型、內(nèi)存屏障等知識(shí)點(diǎn),本文主要介紹單例模式,因此不再贅述,有興趣的同學(xué)可以自行百度
4、靜態(tài)內(nèi)部類public class Singleton { private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } private Singleton() { } public static Singleton getInstance() { return SingletonHolder.INSTANCE; } }
與餓漢式的區(qū)別是,靜態(tài)內(nèi)部類SingletonHolder只有在getInstance()方法第一次調(diào)用的時(shí)候才會(huì)被加載(實(shí)現(xiàn)了延遲加載效果)。
因此靜態(tài)內(nèi)部類實(shí)現(xiàn)方式既能保證線程安全,也能保證單例的唯一性,同時(shí)也具有延遲加載特性
5、枚舉public enum Singleton { INSTANCE; public void doSomething() { System.out.println("doSomething"); } }
優(yōu)點(diǎn):枚舉方式具有以上所有實(shí)現(xiàn)方式的優(yōu)點(diǎn),同時(shí)還無(wú)償?shù)靥峁┝诵蛄谢瘷C(jī)制,防止多次實(shí)例化
缺點(diǎn):JDK1.5以后才支持enum;普及度較前幾種方式不高
優(yōu)點(diǎn)由于單例模式在內(nèi)存中只有一個(gè)實(shí)例,減少了內(nèi)存開支,特別是一個(gè)對(duì)象需要頻繁地創(chuàng)建、銷毀時(shí),而且創(chuàng)建或銷毀時(shí)性能又無(wú)法優(yōu)化,單例模式的優(yōu)勢(shì)就非常明顯。
由于單例模式只生成一個(gè)實(shí)例,所以減少了系統(tǒng)的性能開銷,當(dāng)一個(gè)對(duì)象的產(chǎn)生需要比較多的資源時(shí),如讀取配置、產(chǎn)生其他依賴對(duì)象時(shí),則可以通過(guò)在應(yīng)用啟動(dòng)時(shí)直接產(chǎn)生一個(gè)單例對(duì)象,然后用永久駐留內(nèi)存的方式來(lái)解決(在Java EE中采用單例模式時(shí)需要注意JVM垃圾回收機(jī)制)。
單例模式可以避免對(duì)資源的多重占用,例如一個(gè)寫文件動(dòng)作,由于只有一個(gè)實(shí)例存在內(nèi)存中,避免對(duì)同一個(gè)資源文件的同時(shí)寫操作。
單例模式可以在系統(tǒng)設(shè)置全局的訪問(wèn)點(diǎn),優(yōu)化和共享資源訪問(wèn),例如可以設(shè)計(jì)一個(gè)單例類,負(fù)責(zé)所有數(shù)據(jù)表的映射處理。
缺點(diǎn)單例模式一般沒有接口,擴(kuò)展很困難,若要擴(kuò)展,除了修改代碼基本上沒有第二種途徑可以實(shí)現(xiàn)。單例模式為什么不能增加接口呢?因?yàn)榻涌趯?duì)單例模式是沒有任何意義的,它要求“自行實(shí)例化”,并且提供單一實(shí)例、接口或抽象類是不可能被實(shí)例化的。當(dāng)然,在特殊情況下,單例模式可以實(shí)現(xiàn)接口、被繼承等,需要在系統(tǒng)開發(fā)中根據(jù)環(huán)境判斷。
單例模式對(duì)測(cè)試是不利的。在并行開發(fā)環(huán)境中,如果單例模式?jīng)]有完成,是不能進(jìn)行測(cè)試的,沒有接口也不能使用mock的方式虛擬一個(gè)對(duì)象。
單例模式與單一職責(zé)原則有沖突。一個(gè)類應(yīng)該只實(shí)現(xiàn)一個(gè)邏輯,而不關(guān)心它是否是單例的,是不是要單例取決于環(huán)境,單例模式把“要單例”和業(yè)務(wù)邏輯融合在一個(gè)類中。
使用場(chǎng)景在一個(gè)系統(tǒng)中,要求一個(gè)類有且僅有一個(gè)對(duì)象,如果出現(xiàn)多個(gè)對(duì)象就會(huì)出現(xiàn)“不良反應(yīng)”,可以采用單例模式,具體的場(chǎng)景如下:
要求生成唯一序列號(hào)的環(huán)境;
在整個(gè)項(xiàng)目中需要一個(gè)共享訪問(wèn)點(diǎn)或共享數(shù)據(jù),例如一個(gè)Web頁(yè)面上的計(jì)數(shù)器,可以不用把每次刷新都記錄到數(shù)據(jù)庫(kù)中,使用單例模式保持計(jì)數(shù)器的值,并確保是線程安全的;
創(chuàng)建一個(gè)對(duì)象需要消耗的資源過(guò)多,如要訪問(wèn)IO和數(shù)據(jù)庫(kù)等資源;
需要定義大量的靜態(tài)常量和靜態(tài)方法(如工具類)的環(huán)境,可以采用單例模式(當(dāng)然,也可以直接聲明為static的方式)。
源碼地址:https://gitee.com/tianranll/j...
參考文獻(xiàn):《設(shè)計(jì)模式之禪》、《Effective Java》
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/77739.html
摘要:這個(gè)模式感覺一一般和工廠模式一起使用的比較多比較方便結(jié)構(gòu)型模式這些設(shè)計(jì)模式關(guān)注類和對(duì)象的組合。設(shè)計(jì)模式這些設(shè)計(jì)模式特別關(guān)注表示層。 設(shè)計(jì)模式的的六大原則: 學(xué)習(xí)設(shè)計(jì)模式之前最好先了解一下設(shè)計(jì)模式的設(shè)計(jì)原則: 1. 開閉原則(open close principle) 開放即指對(duì)擴(kuò)展開放,對(duì)修改關(guān)閉 簡(jiǎn)而言之,就是擴(kuò)展功能的時(shí)候應(yīng)該盡量的不修改原有的代碼。 2. 里氏代換原則(lisko...
摘要:現(xiàn)在讓我們?cè)O(shè)置溫度值并將其增加減少幾次小結(jié)在中,單例模式根據(jù)是否懶漢模式餓漢模式以及是否線程安全,分為很多種實(shí)現(xiàn)方式。參考設(shè)計(jì)模式與開發(fā)實(shí)踐設(shè)計(jì)模式 Back in 1994, a book was authored by Erich Gamma, Richard Helm, Ralph Johnson and John Vlissides that discusses 23 desg...
摘要:枚舉推薦優(yōu)點(diǎn)懶加載,線程安全,效率高,大牛推薦作者推薦總結(jié)關(guān)于單例模式的實(shí)現(xiàn)方式,首推的就是枚舉,其次是懶漢模式雙重檢查,最后是靜態(tài)內(nèi)部類 作者:湯圓個(gè)人博客:javalover.cc前言有時(shí)候我們的類并不需要很多個(gè)實(shí)例,在程序運(yùn)行期間,可能只需要一個(gè)實(shí)例就夠了,多了反而會(huì)出現(xiàn)數(shù)據(jù)不一致的問(wèn)題;這時(shí)候我們就可以...
摘要:創(chuàng)建的對(duì)象使構(gòu)造函數(shù)私有,外界將無(wú)法實(shí)例化該類獲得唯一可用的對(duì)象第二步從單例類獲得唯一的對(duì)象。非法構(gòu)造編譯錯(cuò)誤,構(gòu)造函數(shù)不可見。獲得唯一可用對(duì)象展示信息第三步校驗(yàn)輸出。 原文鏈接譯者:smallclover個(gè)人翻譯,水平有限,如有錯(cuò)誤歡迎指出,謝謝! 設(shè)計(jì)模式-單例模式 單例模式是Java中最簡(jiǎn)單的設(shè)計(jì)模式之一。這種類型的設(shè)計(jì)模式,是創(chuàng)建型模式下創(chuàng)建對(duì)象的最好方式之一。這個(gè)模式涉及到一...
摘要:簡(jiǎn)介最基本的實(shí)例中規(guī)定了一個(gè)類只會(huì)被初始化一次所以該方法是線程安全的但是其在方法調(diào)用前就初始化了比較浪費(fèi)資源優(yōu)點(diǎn)只有一個(gè)實(shí)例節(jié)約內(nèi)存空間減少了系統(tǒng)的性能開銷如果某一個(gè)對(duì)象的產(chǎn)生需要比較多的資源時(shí)可以在啟動(dòng)時(shí)直接產(chǎn)生一個(gè)單例對(duì)象使其永駐內(nèi)存可 In software engineering, the singleton pattern is a software design patte...
閱讀 1193·2023-04-26 02:42
閱讀 1637·2021-11-12 10:36
閱讀 1795·2021-10-25 09:47
閱讀 1270·2021-08-18 10:22
閱讀 1810·2019-08-30 15:52
閱讀 1221·2019-08-30 10:54
閱讀 2641·2019-08-29 18:46
閱讀 3503·2019-08-26 18:27