一、什么是單例模式
保證一個(gè)類僅有一個(gè)實(shí)例,并提供了一個(gè)訪問他的全局訪問點(diǎn)。其目的是保證整個(gè)應(yīng)用中只存在類的唯一實(shí)例。
比如我們?cè)谙到y(tǒng)啟動(dòng)時(shí),需要加載一些公共的配置信息,對(duì)整個(gè)應(yīng)用程序的整個(gè)生命周期中都可見且唯一,這時(shí)需要設(shè)計(jì)成單例模式。如:Spring容器,session工廠,緩存,數(shù)據(jù)庫(kù)連接池等等。
二、單例的實(shí)現(xiàn)主要是通過以下兩個(gè)步驟:
1,將該類的構(gòu)造方法定義為私有方法,這樣其他處的代碼就無法通過調(diào)用該類的構(gòu)造方法來實(shí)例化該類的對(duì)象,只有通過該類提供的靜態(tài)方法來得到該類的唯一實(shí)例;
2,在該類內(nèi)提供一個(gè)靜態(tài)方法,當(dāng)我們調(diào)用這個(gè)方法時(shí),如果類持有的引用不為空就返回這個(gè)引用,如果類保持的引用為空就創(chuàng)建該類的實(shí)例并將實(shí)例的引用賦予該類保持的引。
三、適用場(chǎng)景
1,需要生成唯一序列的環(huán)境
2,需要頻繁實(shí)例化然后銷毀代碼的對(duì)象
3,有狀態(tài)的工具類對(duì)象
4,頻繁訪問數(shù)據(jù)庫(kù)或文件的對(duì)象
以下都是單例模式的經(jīng)典實(shí)用場(chǎng)景
1,資源共享的情況下,避免由于資源操作時(shí)導(dǎo)致的性能活損耗。如日志文件,應(yīng)用配置
2,控制資源的情況下,方便資源之間的相互通信。如線程池等。
應(yīng)用場(chǎng)景舉例:
1,外部資源:每臺(tái)計(jì)算機(jī)有若干個(gè)打印機(jī),但是只能有一個(gè)PrinterSpooler,以避免兩個(gè)打印作業(yè)同時(shí)輸出到打印機(jī)。
內(nèi)部資源:大多數(shù)軟件都有一個(gè)或者多個(gè)屬性文件存放系統(tǒng)配置,這樣的系統(tǒng)應(yīng)該有一個(gè)對(duì)象管理這些屬性文件
2,Windows的Task Manager(任務(wù)管理器)就是很典型的單例模式,我們不能同事打開兩個(gè)windows task manager
3,windows的回收站也是典型的單例應(yīng)用,回收站一直維護(hù)著僅有的一個(gè)實(shí)例
4,網(wǎng)站的計(jì)數(shù)器,一般也是采用單例模式來實(shí)現(xiàn),否則難同步
5,應(yīng)用程序的日志應(yīng)用,一般都用單例模式,由于共享的日志文件一直處于打開狀態(tài),因?yàn)橹荒苡幸粋€(gè)實(shí)例去操作,否則內(nèi)容不好追加
6,Web應(yīng)用的配置對(duì)象的讀取,一般也應(yīng)用單例模式,這個(gè)是由于配置文件是共享資源。
7,數(shù)據(jù)庫(kù)連接池的設(shè)計(jì)一般也是采用單例模式,因?yàn)閿?shù)據(jù)庫(kù)連接是一種數(shù)據(jù)庫(kù)資源。數(shù)據(jù)庫(kù)軟件系統(tǒng)中使用數(shù)據(jù)庫(kù)連接池,主要是節(jié)省打開或者關(guān)閉數(shù)據(jù)庫(kù)連接索引引起的效率損耗,這種效率上的消耗是非常昂貴的,因?yàn)橛脝卫J絹砭S護(hù),就可以大大降低這種損耗。
8,多線程的線程池的設(shè)計(jì)一般也是采用單例模式,這是由于線程池要方便對(duì)池中的線程進(jìn)行控制
9,操作系統(tǒng)的文件系統(tǒng),也是大的單例模式實(shí)現(xiàn)的具體例子,一個(gè)操作系統(tǒng)只能有一個(gè)文件系統(tǒng)。
10,HttpApplication也是單例的典型應(yīng)用。熟悉ASP.Net(IIS)的整個(gè)請(qǐng)求生命周期的人應(yīng)該知道,HttpApplication也是單例模式,所有的HttpModule都共享一個(gè)HttpApplication實(shí)例。
四、如何保證實(shí)例的唯一
1,防止外部初始化
2,由類本身進(jìn)行實(shí)例化
3,保證實(shí)例化一次
4,對(duì)外提供獲取實(shí)例的對(duì)象
5,線程返券
五、單例模式的優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
內(nèi)存中只有一個(gè)對(duì)象,節(jié)省內(nèi)存空間
避免頻繁的創(chuàng)建銷毀對(duì)象,可以提高性能
避免對(duì)資源的多重占用,簡(jiǎn)化訪問
為整個(gè)系統(tǒng)提供一個(gè)全局訪問點(diǎn)
缺點(diǎn):
不適用于變化頻繁的對(duì)象;
濫用單利將帶來一些問題,如為了節(jié)省資源將數(shù)據(jù)庫(kù)連接池對(duì)象設(shè)計(jì)為的單利類,可能會(huì)導(dǎo)致共享連接池對(duì)象的程序過多而出現(xiàn)連接池溢出;
如果實(shí)例化的對(duì)象長(zhǎng)時(shí)間不被利用,系統(tǒng)會(huì)認(rèn)為該對(duì)象是垃圾而被回收,這可能會(huì)導(dǎo)致對(duì)象狀態(tài)的丟失;
六、單例模式的實(shí)現(xiàn)
1,餓漢式
// 餓漢式單例
public class Singleton1 {
? ? // 指向自己實(shí)例的私有靜態(tài)引用,主動(dòng)創(chuàng)建
? ? private static Singleton1 singleton1 = new Singleton1();
? ? // 私有的構(gòu)造方法
? ? private Singleton1(){}
? ? // 以自己實(shí)例為返回值的靜態(tài)的公有方法,靜態(tài)工廠方法
? ? public static Singleton1 getSingleton1(){
? ? ? ? return singleton1;
? ? }
}
我們知道,類加載的方式是需加載,切加載一次。。因此,在上述單例被加載時(shí),就會(huì)實(shí)例化一個(gè)對(duì)象并交給自己的引用,供系統(tǒng)使用;而且,由于來在整個(gè)生命周期中只會(huì)被加載一次,因此只會(huì)創(chuàng)建一個(gè)實(shí)例,即能夠充分保證單例。
優(yōu)點(diǎn):這種寫法比較簡(jiǎn)單,就是在類裝載的時(shí)候就完成實(shí)例化。避免了線程同步問題。
缺點(diǎn):在類裝載的時(shí)候就完成實(shí)例化,沒有達(dá)到Lazy Loading的效果。如果喲偶的類從始至終都未使用過這個(gè)實(shí)例,則會(huì)造成內(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(){
? ? ? ? // 被動(dòng)創(chuàng)建,在真正需要使用時(shí)才去創(chuàng)建
? ? ? ? if (singleton2 == null) {
? ? ? ? ? ? singleton2 = new Singleton2();
? ? ? ? }
? ? ? ? return singleton2;
? ? }
}
我們從懶漢式單例可以看到,單例實(shí)例被延遲加載,即只有在真正使用的時(shí)候才會(huì)實(shí)例化一個(gè)對(duì)象并交給自己的引用。
這種寫法起到了Lazy Loading的效果,但是只能在單線程下使用。如果多線程下,一個(gè)線程進(jìn)入了if(singletion==null)判斷語(yǔ)句塊,還未來得及往下執(zhí)行,另一個(gè)線程也通過了這個(gè)判斷語(yǔ)句,這時(shí)變回產(chǎn)生多個(gè)實(shí)例。所以在多線程下不可用這種方式。
3,接下來對(duì)它進(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)行時(shí)創(chuàng)建一個(gè)靜態(tài)只讀的進(jìn)程輔助對(duì)象
? ? ? ? private static readonly object syncRoot = new object();
? ? ? ? private Singleton() { }
? ? ? ? public static Singleton GetInstance()
? ? ? ? { //先判斷是否存在,不存在再加鎖處理
? ? ? ? ? if (instance == null)
? ? ? ? ? ? {
? ? ? ? ? ? ? ? //在同一個(gè)時(shí)刻加了鎖的那部分程序只有一個(gè)線程可以進(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í)行一次,后面再次訪問時(shí),判斷if(singletion==null)直接return實(shí)例化對(duì)象。
使用了雙重檢測(cè)同步延遲加載去創(chuàng)建單例的做法是一個(gè)非常優(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):既避免了同步帶來的性能損耗,又能夠延遲加載