一、什么是單例模式
保證一個類僅有一個實(shí)例,并提供了一個訪問他的全局訪問點(diǎn)嫩舟。其目的是保證整個應(yīng)用中只存在類的唯一實(shí)例。
比如我們在系統(tǒng)啟動時怀偷,需要加載一些公共的配置信息家厌,對整個應(yīng)用程序的整個生命周期中都可見且唯一,這時需要設(shè)計成單例模式椎工。如:Spring容器饭于,session工廠,緩存维蒙,數(shù)據(jù)庫連接池等等掰吕。
二、單例的實(shí)現(xiàn)主要是通過以下兩個步驟:
1颅痊,將該類的構(gòu)造方法定義為私有方法殖熟,這樣其他處的代碼就無法通過調(diào)用該類的構(gòu)造方法來實(shí)例化該類的對象,只有通過該類提供的靜態(tài)方法來得到該類的唯一實(shí)例斑响;
2菱属,在該類內(nèi)提供一個靜態(tài)方法,當(dāng)我們調(diào)用這個方法時舰罚,如果類持有的引用不為空就返回這個引用纽门,如果類保持的引用為空就創(chuàng)建該類的實(shí)例并將實(shí)例的引用賦予該類保持的引。
三营罢、適用場景
1赏陵,需要生成唯一序列的環(huán)境
2,需要頻繁實(shí)例化然后銷毀代碼的對象
3饲漾,有狀態(tài)的工具類對象
4瘟滨,頻繁訪問數(shù)據(jù)庫或文件的對象
以下都是單例模式的經(jīng)典實(shí)用場景
1,資源共享的情況下能颁,避免由于資源操作時導(dǎo)致的性能活損耗。如日志文件倒淫,應(yīng)用配置
2伙菊,控制資源的情況下,方便資源之間的相互通信。如線程池等镜硕。
應(yīng)用場景舉例:
1运翼,外部資源:每臺計算機(jī)有若干個打印機(jī),但是只能有一個PrinterSpooler兴枯,以避免兩個打印作業(yè)同時輸出到打印機(jī)血淌。
內(nèi)部資源:大多數(shù)軟件都有一個或者多個屬性文件存放系統(tǒng)配置,這樣的系統(tǒng)應(yīng)該有一個對象管理這些屬性文件
2财剖,Windows的Task Manager(任務(wù)管理器)就是很典型的單例模式悠夯,我們不能同事打開兩個windows task manager
3,windows的回收站也是典型的單例應(yīng)用躺坟,回收站一直維護(hù)著僅有的一個實(shí)例
4沦补,網(wǎng)站的計數(shù)器,一般也是采用單例模式來實(shí)現(xiàn)咪橙,否則難同步
5夕膀,應(yīng)用程序的日志應(yīng)用,一般都用單例模式美侦,由于共享的日志文件一直處于打開狀態(tài)产舞,因?yàn)橹荒苡幸粋€實(shí)例去操作,否則內(nèi)容不好追加
6菠剩,Web應(yīng)用的配置對象的讀取易猫,一般也應(yīng)用單例模式,這個是由于配置文件是共享資源赠叼。
7擦囊,數(shù)據(jù)庫連接池的設(shè)計一般也是采用單例模式,因?yàn)閿?shù)據(jù)庫連接是一種數(shù)據(jù)庫資源嘴办。數(shù)據(jù)庫軟件系統(tǒng)中使用數(shù)據(jù)庫連接池瞬场,主要是節(jié)省打開或者關(guān)閉數(shù)據(jù)庫連接索引引起的效率損耗,這種效率上的消耗是非常昂貴的涧郊,因?yàn)橛脝卫J絹砭S護(hù)挠羔,就可以大大降低這種損耗。
8笋庄,多線程的線程池的設(shè)計一般也是采用單例模式旷太,這是由于線程池要方便對池中的線程進(jìn)行控制
9,操作系統(tǒng)的文件系統(tǒng)批旺,也是大的單例模式實(shí)現(xiàn)的具體例子幌陕,一個操作系統(tǒng)只能有一個文件系統(tǒng)。
10汽煮,HttpApplication也是單例的典型應(yīng)用搏熄。熟悉ASP.Net(IIS)的整個請求生命周期的人應(yīng)該知道棚唆,HttpApplication也是單例模式,所有的HttpModule都共享一個HttpApplication實(shí)例心例。
四宵凌、如何保證實(shí)例的唯一
1,防止外部初始化
2止后,由類本身進(jìn)行實(shí)例化
3瞎惫,保證實(shí)例化一次
4,對外提供獲取實(shí)例的對象
5译株,線程返券
五瓜喇、單例模式的優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
內(nèi)存中只有一個對象,節(jié)省內(nèi)存空間
避免頻繁的創(chuàng)建銷毀對象古戴,可以提高性能
避免對資源的多重占用欠橘,簡化訪問
為整個系統(tǒng)提供一個全局訪問點(diǎn)
缺點(diǎn):
不適用于變化頻繁的對象;
濫用單利將帶來一些問題现恼,如為了節(jié)省資源將數(shù)據(jù)庫連接池對象設(shè)計為的單利類肃续,可能會導(dǎo)致共享連接池對象的程序過多而出現(xiàn)連接池溢出;
如果實(shí)例化的對象長時間不被利用叉袍,系統(tǒng)會認(rèn)為該對象是垃圾而被回收始锚,這可能會導(dǎo)致對象狀態(tài)的丟失;
六喳逛、單例模式的實(shí)現(xiàn)
1瞧捌,餓漢式
// 餓漢式單例
public class Singleton1 {
? ? // 指向自己實(shí)例的私有靜態(tài)引用,主動創(chuàng)建
? ? private static Singleton1 singleton1 = new Singleton1();
? ? // 私有的構(gòu)造方法
? ? private Singleton1(){}
? ? // 以自己實(shí)例為返回值的靜態(tài)的公有方法润文,靜態(tài)工廠方法
? ? public static Singleton1 getSingleton1(){
? ? ? ? return singleton1;
? ? }
}
我們知道姐呐,類加載的方式是需加載,切加載一次典蝌。曙砂。因此,在上述單例被加載時骏掀,就會實(shí)例化一個對象并交給自己的引用鸠澈,供系統(tǒng)使用;而且截驮,由于來在整個生命周期中只會被加載一次笑陈,因此只會創(chuàng)建一個實(shí)例,即能夠充分保證單例葵袭。
優(yōu)點(diǎn):這種寫法比較簡單涵妥,就是在類裝載的時候就完成實(shí)例化。避免了線程同步問題坡锡。
缺點(diǎn):在類裝載的時候就完成實(shí)例化妹笆,沒有達(dá)到Lazy Loading的效果块请。如果喲偶的類從始至終都未使用過這個實(shí)例,則會造成內(nèi)存浪費(fèi)拳缠。
2,懶漢式
// 懶漢式單例
public class Singleton2 {
? ? // 指向自己實(shí)例的私有靜態(tài)引用
? ? private static Singleton2 singleton2;
? ? // 私有的構(gòu)造方法
? ? private Singleton2(){}
? ? // 以自己實(shí)例為返回值的靜態(tài)的公有方法贸弥,靜態(tài)工廠方法
? ? public static Singleton2 getSingleton2(){
? ? ? ? // 被動創(chuàng)建窟坐,在真正需要使用時才去創(chuàng)建
? ? ? ? if (singleton2 == null) {
? ? ? ? ? ? singleton2 = new Singleton2();
? ? ? ? }
? ? ? ? return singleton2;
? ? }
}
我們從懶漢式單例可以看到,單例實(shí)例被延遲加載绵疲,即只有在真正使用的時候才會實(shí)例化一個對象并交給自己的引用哲鸳。
這種寫法起到了Lazy Loading的效果,但是只能在單線程下使用盔憨。如果多線程下徙菠,一個線程進(jìn)入了if(singletion==null)判斷語句塊,還未來得及往下執(zhí)行郁岩,另一個線程也通過了這個判斷語句婿奔,這時變回產(chǎn)生多個實(shí)例。所以在多線程下不可用這種方式问慎。
3萍摊,接下來對它進(jìn)行線程安全改造
(1)同步鎖
public synchronized static Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
優(yōu)點(diǎn):線程安全
缺點(diǎn):每次獲取實(shí)例都要枷鎖,耗費(fèi)資源如叼,其實(shí)主要實(shí)例已經(jīng)生成冰木,以后獲取就不需要在鎖了。
(2)雙重檢查鎖
public class Singleton
? ? {
? ? ? ? private static Singleton instance;
? ? ? ? //程序運(yùn)行時創(chuàng)建一個靜態(tài)只讀的進(jìn)程輔助對象
? ? ? ? private static readonly object syncRoot = new object();
? ? ? ? private Singleton() { }
? ? ? ? public static Singleton GetInstance()
? ? ? ? { //先判斷是否存在笼恰,不存在再加鎖處理
? ? ? ? ? if (instance == null)
? ? ? ? ? ? {
? ? ? ? ? ? ? ? //在同一個時刻加了鎖的那部分程序只有一個線程可以進(jìn)入
? ? ? ? ? ? ? ? lock (syncRoot)
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? if (instance == null)
? ? ? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? ? ? instance = new Singleton();
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? ? ? return instance;
? ? ? ? }
? ? }
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
代碼中我們進(jìn)行了兩次if(singletion==null)檢查踊沸,這樣就可以保證線程安全了。這樣社证,實(shí)例化代碼只執(zhí)行一次逼龟,后面再次訪問時,判斷if(singletion==null)直接return實(shí)例化對象猴仑。
使用了雙重檢測同步延遲加載去創(chuàng)建單例的做法是一個非常優(yōu)秀的做法审轮,其不但保證了單例,而且切實(shí)提高了程序運(yùn)行效率
優(yōu)點(diǎn):線程安全辽俗;延遲加載疾渣;效率較高。
(3)靜態(tài)內(nèi)部類
public class Singleton {
private Singleton() {
}
private static class SingletonHolder {
private static final Singleton singleton = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.singleton;
}
}
優(yōu)點(diǎn):既避免了同步帶來的性能損耗崖飘,又能夠延遲加載