目錄
本文的結(jié)構(gòu)如下:
- 什么是單例模式
- 為什么要用該模式
- 模式的結(jié)構(gòu)
- 代碼示例
- 優(yōu)點(diǎn)和缺點(diǎn)
- 適用環(huán)境
- 模式應(yīng)用
- 總結(jié)
一僻弹、前言
對于系統(tǒng)中的某些類來說芽淡,只有一個(gè)實(shí)例很重要唆香,例如,Windows任務(wù)管理器璃饱。通常情況下骏全,無論我們啟動任務(wù)管理多少次苍柏,Windows系統(tǒng)始終只能彈出一個(gè)任務(wù)管理器窗口,也就是說在一個(gè)Windows系統(tǒng)中姜贡,任務(wù)管理器存在唯一性试吁。為什么要這樣設(shè)計(jì)呢?
其一,如果能彈出多個(gè)窗口熄捍,且這些窗口的內(nèi)容完全一致烛恤,全部是重復(fù)對象,這勢必會浪費(fèi)系統(tǒng)資源余耽,任務(wù)管理器需要獲取系統(tǒng)運(yùn)行時(shí)的諸多信息缚柏,這些信息的獲取需要消耗一定的系統(tǒng)資源,包括CPU資源及內(nèi)存資源等碟贾,浪費(fèi)是可恥的币喧,而且根本沒有必要顯示多個(gè)內(nèi)容完全相同的窗口;
其二袱耽,如果彈出的多個(gè)窗口內(nèi)容不一致杀餐,問題就更加嚴(yán)重了,這意味著在某一瞬間系統(tǒng)資源使用情況和進(jìn)程扛邑、服務(wù)等信息存在多個(gè)狀態(tài),例如任務(wù)管理器窗口A顯示“CPU使用率”為10%铐然,窗口B顯示“CPU使用率”為15%蔬崩,到底哪個(gè)才是真實(shí)的呢?這會誤導(dǎo)用戶搀暑。
在實(shí)際開發(fā)中沥阳,也經(jīng)常有類似的情況,為了節(jié)約系統(tǒng)資源自点,有時(shí)需要確保系統(tǒng)中某個(gè)類只有唯一一個(gè)實(shí)例桐罕,當(dāng)這個(gè)唯一實(shí)例創(chuàng)建成功之后,就無法再創(chuàng)建一個(gè)同類型的其他對象桂敛,所有的操作都只能基于這個(gè)唯一實(shí)例功炮。為了確保對象的唯一性,可以通過單例模式來實(shí)現(xiàn)术唬。
二薪伏、什么是單例模式
單例模式相對簡單,它確保某一個(gè)類只有一個(gè)實(shí)例粗仓,而且自行實(shí)例化并向整個(gè)系統(tǒng)提供這個(gè)實(shí)例嫁怀,這個(gè)類稱為單例類,它提供全局訪問的方法借浊。
單例模式的要點(diǎn)有三個(gè):
- 一是某個(gè)類只能有一個(gè)實(shí)例塘淑;
- 二是它必須自行創(chuàng)建這個(gè)實(shí)例;
- 三是它必須自行向整個(gè)系統(tǒng)提供這個(gè)實(shí)例蚂斤。
單例模式是一種對象創(chuàng)建型模式存捺。單例模式又名單件模式或單態(tài)模式。
三曙蒸、為什么要用該模式
前言中已經(jīng)介紹過召噩,在開發(fā)軟件中母赵,可能有些類的創(chuàng)建十分消耗系統(tǒng)資源,或者初始化時(shí)間較長具滴,像線程池凹嘲,數(shù)據(jù)庫連接池,秉著節(jié)約的態(tài)度构韵,為了提升軟件的性能周蹭,一般只允許這些類只被創(chuàng)建出一個(gè)實(shí)例,且能夠全局共享疲恢。
四凶朗、模式的結(jié)構(gòu)
單例模式的結(jié)構(gòu)簡單,只有一個(gè)類显拳。
五棚愤、代碼示例
單例模式結(jié)構(gòu)雖然簡單,但代碼里面還是有許多道道杂数,有好幾種寫法宛畦,下面具體看看:
5.1、餓漢式
/**
* Created by w1992wishes on 2017/11/2.
*/
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return instance;
}
}
這種方式和名字很貼切揍移,饑不擇食次和,在類裝載的時(shí)候就創(chuàng)建,不管用不用那伐,先創(chuàng)建了再說踏施。
缺點(diǎn)是:如果一直沒有被使用,便浪費(fèi)了空間罕邀,典型的空間換時(shí)間畅形,如果初始化很耗費(fèi)時(shí)間,同時(shí)也會推遲系統(tǒng)的啟動時(shí)間诉探。
優(yōu)點(diǎn)是:每次調(diào)用的時(shí)候束亏,就不需要再判斷,節(jié)省了運(yùn)行時(shí)間阵具。
java Runtime使用的就是“餓漢式”單例:
5.2碍遍、懶漢式(非線程安全)
/**
* Created by w1992wishes on 2017/11/2.
*/
public class Singleton {
private static Singleton instance;
private Singleton(){}
public static Singleton getInstance(){
if (instance == null){
instance = new Singleton();
}
return instance;
}
}
懶漢模式申明了一個(gè)靜態(tài)對象,在用戶第一次調(diào)用時(shí)初始化阳液。
缺點(diǎn)是:第一次加載時(shí)需要實(shí)例化怕敬,反映稍慢一些,而且在多線程不能正常工作帘皿,很可能會造成多次實(shí)例化东跪,就不再是單例了。
優(yōu)點(diǎn)是:使用時(shí)才創(chuàng)建,節(jié)約了資源虽填。
“懶漢式”與“餓漢式”的最大區(qū)別就是將單例的初始化操作丁恭,延遲到需要的時(shí)候才進(jìn)行,這樣做在某些場合中有很大用處斋日。比如某個(gè)單例用的次數(shù)不是很多牲览,但是這個(gè)單例提供的功能又非常復(fù)雜,而且加載和初始化要消耗大量的資源恶守,這個(gè)時(shí)候使用“懶漢式”就是非常不錯(cuò)的選擇第献。
5.3、懶漢式(線程安全)
/**
* Created by w1992wishes on 2017/11/2.
*/
public class Singleton {
private static Singleton instance;
private Singleton(){}
public static synchronized Singleton getInstance(){
if (instance == null){
instance = new Singleton();
}
return instance;
}
}
在“懶漢式(非線程安全)”的基礎(chǔ)上加上一個(gè)synchronized修飾兔港,就是線程安全的了庸毫。
缺點(diǎn)是:其實(shí)只有第一次調(diào)用getInstance時(shí)才真正需要同步,一旦設(shè)置好instance實(shí)例后衫樊,之后每次同步都是累贅飒赃,平白增添性能消耗。
優(yōu)點(diǎn)是:可以在多線程環(huán)境下確保只初始化一個(gè)單例科侈。
5.4载佳、雙重校驗(yàn)鎖(DCL)
/**
* 注意此處使用的關(guān)鍵字 volatile,它保證了可見性
* 被volatile修飾的變量的值兑徘,將不會被本地線程緩存刚盈,
* 所有對該變量的讀寫都是直接操作共享內(nèi)存羡洛,從而確保多個(gè)線程能正確的處理該變量挂脑。
* Created by w1992wishes on 2017/11/2.
*/
public class Singleton {
private static volatile Singleton instance;
private Singleton(){}
public static Singleton getInstance(){
if (instance == null){
synchronized (Singleton.class){
if (instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
如果程序可以接受synchronized帶來的性能負(fù)擔(dān),“懶漢式(線程安全)”可以使用欲侮,但如果很關(guān)心性能崭闲,“雙重校驗(yàn)鎖(DCL)”將會帶來很大幫助。
在getInstance()方法中對instance進(jìn)行了兩次判空威蕉,第一次是為了不必要的同步刁俭,因?yàn)橹挥械谝淮握{(diào)用getInstance()方法才需要同步,第二次是在singleton等于null的情況下才創(chuàng)建實(shí)例韧涨。
缺點(diǎn)是:代碼相對其他較為復(fù)雜牍戚,有一定的性能損耗。
優(yōu)點(diǎn)是:既可以達(dá)到線程安全虑粥,也可以使性能不受很大的影響如孝,換句話說在保證線程安全的前提下,既節(jié)省空間也節(jié)省了時(shí)間娩贷。
5.5第晰、靜態(tài)內(nèi)部類
/**
* Created by w1992wishes on 2017/11/2.
*/
public class Singleton {
private Singleton(){}
public static Singleton getInstance(){
return SingletonHolder.sInstance;
}
private static class SingletonHolder{
private static final Singleton sInstance = new Singleton();
}
}
缺點(diǎn)是:與編程語言本身的特性相關(guān),很多面向?qū)ο笳Z言不支持。
優(yōu)點(diǎn)是:由于靜態(tài)單例對象沒有作為Singleton的成員變量直接實(shí)例化茁瘦,因此類加載時(shí)不會實(shí)例化Singleton品抽,第一次調(diào)用getInstance()時(shí)將加載內(nèi)部類SingletonHolder,在該內(nèi)部類中定義了一個(gè)static類型的變量sInstance甜熔,此時(shí)會首先初始化這個(gè)成員變量圆恤,由Java虛擬機(jī)來保證其線程安全性,確保該成員變量只能初始化一次纺非。由于getInstance()方法沒有任何線程鎖定哑了,因此其性能不會造成任何影響。
5.6烧颖、枚舉
/**
* Created by w1992wishes on 2017/11/2.
*/
public enum Singleton {
INSTANCE;//定義一個(gè)枚舉的元素弱左,它就是 Singleton 的一個(gè)實(shí)例
public void doSomething(){}
}
使用如下:
public static void main(String args[]) {
Singleton singleton = Singleton.instance;
singleton.doSomeThing();
}
缺點(diǎn)是:大部分應(yīng)用開發(fā)很少用枚舉,可讀性并不是很高炕淮。
優(yōu)點(diǎn)是:使用枚舉來實(shí)現(xiàn)單實(shí)例控制會更加簡潔拆火,而且無償?shù)靥峁┝诵蛄谢瘷C(jī)制,并由JVM從根本上提供保障涂圆,絕對防止多次實(shí)例化们镜,是更簡潔、高效润歉、安全的實(shí)現(xiàn)單例的方式模狭。
5.7、使用容器
/**
* Created by w1992wishes on 2017/11/2.
*/
public class SingletonManager {
private static Map<String, Object> cache = new HashMap<String, Object>();
private SingletonManager(){}
public static void registerInstance(String key, Object instance){
if (!cache.containsKey(key)){
cache.put(key, instance);
}
}
public static Object getInstance(String key){
return cache.get(key);
}
}
這種是用SingletonManager 將多種單例類統(tǒng)一管理踩衩,在使用時(shí)根據(jù)key獲取對象對應(yīng)類型的對象嚼鹉。
缺點(diǎn)是:這也不是線程安全的,當(dāng)然可以用ConcurrentHashMap改進(jìn)驱富。
優(yōu)點(diǎn)是:這種方式使得我們可以管理多種類型的單例锚赤,并且在使用時(shí)可以通過統(tǒng)一的接口進(jìn)行獲取操作,降低了用戶的使用成本褐鸥,也對用戶隱藏了具體實(shí)現(xiàn)线脚,降低了耦合度。
六叫榕、優(yōu)點(diǎn)和缺點(diǎn)
6.1浑侥、優(yōu)點(diǎn)
- 單例模式提供了對唯一實(shí)例的受控訪問。因?yàn)閱卫惙庋b了它的唯一實(shí)例晰绎,所以它可以嚴(yán)格控制客戶怎樣以及何時(shí)訪問它寓落。
- 由于在系統(tǒng)內(nèi)存中只存在一個(gè)對象,因此可以節(jié)約系統(tǒng)資源寒匙,對于一些需要頻繁創(chuàng)建和銷毀的對象單例模式無疑可以提高系統(tǒng)的性能零如。
- 允許可變數(shù)目的實(shí)例躏将。基于單例模式我們可以進(jìn)行擴(kuò)展考蕾,使用與單例控制相似的方法來獲得指定個(gè)數(shù)的對象實(shí)例祸憋,既節(jié)省系統(tǒng)資源,又解決了單例單例對象共享過多有損性能的問題肖卧。
6.2蚯窥、缺點(diǎn)
- 由于單例模式中沒有抽象層,因此單例類的擴(kuò)展有很大的困難塞帐。
- 單例類的職責(zé)過重拦赠,在一定程度上違背了“單一職責(zé)原則”。因?yàn)閱卫惣瘸洚?dāng)了工廠角色葵姥,提供了工廠方法荷鼠,同時(shí)又充當(dāng)了產(chǎn)品角色,包含一些業(yè)務(wù)方法榔幸,將產(chǎn)品的創(chuàng)建和產(chǎn)品的本身的功能融合到一起允乐。
- 濫用單例將帶來一些負(fù)面問題,如為了節(jié)省資源將數(shù)據(jù)庫連接池對象設(shè)計(jì)為單例類削咆,可能會導(dǎo)致共享連接池對象的程序過多而出現(xiàn)連接池溢出牍疏。
- 現(xiàn)在很多面向?qū)ο笳Z言(如Java、C#)的運(yùn)行環(huán)境都提供了自動垃圾回收的技術(shù)拨齐,因此鳞陨,如果實(shí)例化的共享對象長時(shí)間不被利用,系統(tǒng)會認(rèn)為它是垃圾瞻惋,會自動銷毀并回收資源厦滤,下次利用時(shí)又將重新實(shí)例化,這將導(dǎo)致共享的單例對象狀態(tài)的丟失熟史。
七馁害、適用環(huán)境
在以下情況下可以使用單例模式:
- 系統(tǒng)只需要一個(gè)實(shí)例對象窄俏,如系統(tǒng)要求提供一個(gè)唯一的序列號生成器蹂匹,或者需要考慮資源消耗太大而只允許創(chuàng)建一個(gè)對象。
- 客戶調(diào)用類的單個(gè)實(shí)例只允許使用一個(gè)公共訪問點(diǎn)凹蜈,除了該公共訪問點(diǎn)限寞,不能通過其他途徑訪問該實(shí)例。
在一個(gè)系統(tǒng)中要求一個(gè)類只有一個(gè)實(shí)例時(shí)才應(yīng)當(dāng)使用單例模式仰坦。反過來履植,如果一個(gè)類可以有幾個(gè)實(shí)例共存,就需要對單例模式進(jìn)行改進(jìn)悄晃,使之成為多例模式玫霎。
八凿滤、模式應(yīng)用
一個(gè)具有自動編號主鍵的表可以有多個(gè)用戶同時(shí)使用,但數(shù)據(jù)庫中只能有一個(gè)地方分配下一個(gè)主鍵編號庶近,否則會出現(xiàn)主鍵重復(fù)翁脆,因此該主鍵編號生成器必須具備唯一性,可以通過單例模式來實(shí)現(xiàn)鼻种。
九反番、總結(jié)
- 單例模式確保某一個(gè)類只有一個(gè)實(shí)例,而且自行實(shí)例化并向整個(gè)系統(tǒng)提供這個(gè)實(shí)例叉钥,這個(gè)類稱為單例類罢缸,它提供全局訪問的方法。單例模式的要點(diǎn)有三個(gè):一是某個(gè)類只能有一個(gè)實(shí)例投队;二是它必須自行創(chuàng)建這個(gè)實(shí)例枫疆;三是它必須自行向整個(gè)系統(tǒng)提供這個(gè)實(shí)例。單例模式是一種對象創(chuàng)建型模式敷鸦。
- 單例模式只包含一個(gè)單例角色:在單例類的內(nèi)部實(shí)現(xiàn)只生成一個(gè)實(shí)例养铸,同時(shí)它提供一個(gè)靜態(tài)的工廠方法,讓客戶可以使用它的唯一實(shí)例轧膘;為了防止在外部對其實(shí)例化钞螟,將其構(gòu)造函數(shù)設(shè)計(jì)為私有。
- 單例模式的目的是保證一個(gè)類僅有一個(gè)實(shí)例谎碍,并提供一個(gè)訪問它的全局訪問點(diǎn)鳞滨。單例類擁有一個(gè)私有構(gòu)造函數(shù),確保用戶無法通過new關(guān)鍵字直接實(shí)例化它蟆淀。除此之外拯啦,該模式中包含一個(gè)靜態(tài)私有成員變量與靜態(tài)公有的工廠方法。該工廠方法負(fù)責(zé)檢驗(yàn)實(shí)例的存在性并實(shí)例化自己熔任,然后存儲在靜態(tài)成員變量中褒链,以確保只有一個(gè)實(shí)例被創(chuàng)建。
- 單例模式的主要優(yōu)點(diǎn)在于提供了對唯一實(shí)例的受控訪問并可以節(jié)約系統(tǒng)資源疑苔;其主要缺點(diǎn)在于因?yàn)槿鄙俪橄髮佣y以擴(kuò)展甫匹,且單例類職責(zé)過重。
- 單例模式適用情況包括:系統(tǒng)只需要一個(gè)實(shí)例對象惦费;客戶調(diào)用類的單個(gè)實(shí)例只允許使用一個(gè)公共訪問點(diǎn)兵迅。