之前在開發(fā)中老用到一些設(shè)計(jì)模式可是呢又不是很懂,于是狠下心來琢磨一番金砍。下面是我琢磨后總結(jié)的局蚀,希望對(duì)您有用。如果發(fā)現(xiàn)了問題捞魁,請(qǐng)幫忙指正至会。
一、單例模式是什么谱俭?
單例模式最初的定義出現(xiàn)于《設(shè)計(jì)模式》:“保證一個(gè)類僅有一個(gè)實(shí)例奉件,并提供一個(gè)訪問它的全局訪問點(diǎn)±ブ”
Java中單例模式定義县貌;“一個(gè)類有且僅有一個(gè)實(shí)例,并且自行實(shí)例化向整個(gè)系統(tǒng)提供該實(shí)例凑懂∶汉郏”
二、為什么用單例模式接谨?
對(duì)于系統(tǒng)中的某些類來說摆碉,只有一個(gè)實(shí)例很重要。例如脓豪,一個(gè)系統(tǒng)中可以存在多個(gè)打印任務(wù)巷帝,但是只能有一個(gè)正在工作的任務(wù);一個(gè)系統(tǒng)只有有一個(gè)窗口管理器或文件系統(tǒng)扫夜;一個(gè)系統(tǒng)只能有一個(gè)計(jì)時(shí)工具或ID生成器楞泼。如在Windows OS 中就只能打開一個(gè)任務(wù)管理器。如果不使用機(jī)制對(duì)窗口對(duì)象進(jìn)行唯一化笤闯,將彈出多個(gè)窗口堕阔,如果這些窗口顯示的內(nèi)容完全一致,則重復(fù)對(duì)象颗味,浪費(fèi)內(nèi)存資源超陆;如果這些窗口顯示的內(nèi)容不一致,則意味著某一瞬間系統(tǒng)有多個(gè)狀態(tài)脱衙,與實(shí)際不符侥猬,也會(huì)為用戶帶來誤解例驹,不知道哪一個(gè)才是真實(shí)的狀態(tài)。因此有時(shí)確保系統(tǒng)中某個(gè)對(duì)象的唯一性即一個(gè)類只能有一個(gè)實(shí)例是非常重要的退唠。
如何保證一個(gè)類只有一個(gè)實(shí)例并且這個(gè)實(shí)例易于被訪問呢鹃锈?定義一個(gè)全局變量可以確保對(duì)象隨時(shí)都可以被訪問,但不能防止我們實(shí)例化多個(gè)對(duì)象瞧预。一個(gè)更好的解決辦法是讓類自身負(fù)責(zé)保存它的唯一實(shí)例屎债。這個(gè)類可以保證沒有其他實(shí)例被創(chuàng)建,并且它可以提供一個(gè)訪問該實(shí)例的方法垢油。這就是單例模式的模式動(dòng)機(jī)盆驹。
三、單例模式特點(diǎn)
單例模式特點(diǎn)有三個(gè)
1滩愁、單例類只能有一個(gè)實(shí)例躯喇。
2、單例類必須自己創(chuàng)建自己的唯一實(shí)例硝枉。
3廉丽、單例類必須給其他對(duì)象(整個(gè)系統(tǒng))提供這一實(shí)例。
從具體實(shí)現(xiàn)角度分析妻味,一是單例模式的類只提供私有的(private)構(gòu)造函數(shù)正压,二是類定義中含有一個(gè)該類的靜態(tài)私有(private static)對(duì)象,三是該類提供了一個(gè)靜態(tài)的(static)公有的(public)函數(shù)用于創(chuàng)建或獲取它本身的靜態(tài)私有對(duì)象责球。
四焦履、Java中幾種常見單例模式寫法
通過上面的介紹你是不是對(duì)單例模式有了一個(gè)總的概念?沒有雏逾,那接下來繼續(xù)給你們放大招嘉裤。
基于單例模式特點(diǎn),單例對(duì)象通常作為程序中存放配置信息的載體(想想Android中的Application經(jīng)常在里面做一些配置的初始化)栖博,因?yàn)樗軌虮WC其他對(duì)象讀取到一致的信息价脾。例如在某個(gè)服務(wù)器程序中,該服務(wù)器的配置信息可能存放在數(shù)據(jù)庫或 文件中笛匙,這些配置數(shù)據(jù)由某個(gè)單例對(duì)象統(tǒng)一讀取,服務(wù)進(jìn)程中的其他對(duì)象如果要獲取這些配置信息犀变,只需訪問該單例對(duì)象即可妹孙。這種方式極大地簡(jiǎn)化了在復(fù)雜環(huán)境 下,尤其是多線程環(huán)境下的配置管理获枝,但是隨著應(yīng)用場(chǎng)景的不同蠢正,也可能帶來一些同步問題。
1省店、餓漢式單例
//餓漢式單例類.在類初始化時(shí)嚣崭,已經(jīng)自行實(shí)例化
public class Singleton {
//私有的默認(rèn)構(gòu)造子
private Singleton() {}
//已經(jīng)自行實(shí)例化
private static final Singleton single = new Singleton();
//靜態(tài)工廠方法
public static Singleton getInstance() {
return single;
}
}
上面例子中笨触,在這個(gè)類被加載時(shí),靜態(tài)變量single會(huì)被初始化雹舀,此時(shí)類的私有構(gòu)造子會(huì)被調(diào)用芦劣。這時(shí)單例類的唯一實(shí)例就被構(gòu)造出來了。
餓漢式其實(shí)是一種比較形象的稱謂说榆。既然餓虚吟,那么在創(chuàng)建對(duì)象實(shí)例的時(shí)候就比較著急,餓了嘛签财,于是在裝載類的時(shí)候就創(chuàng)建對(duì)象實(shí)例--->
private static final Singleton single = new Singleton();
餓漢式是典型的空間換時(shí)間串慰,當(dāng)類裝載時(shí)就會(huì)創(chuàng)建類的實(shí)例,不管你用不用唱蒸,先創(chuàng)建出來邦鲫,然后每次調(diào)用的時(shí)候,就不需要再判斷神汹,節(jié)省了運(yùn)行時(shí)間庆捺。
2、懶漢式單例
//懶漢式單例類.在第一次調(diào)用的時(shí)候?qū)嵗约?
public class Singleton {
private Singleton() {}
private static Singleton single=null;
//靜態(tài)工廠方法
public static synchronized Singleton getInstance() {
if (single == null) {
single = new Singleton();
}
return single;
}
}
上面的懶漢式單例類實(shí)現(xiàn)里對(duì)靜態(tài)工廠方法使用了同步化慎冤,以處理多線程環(huán)境疼燥。
懶漢式其實(shí)是一種比較形象的稱謂。既然懶蚁堤,那么在創(chuàng)建對(duì)象實(shí)例的時(shí)候就不著急醉者。會(huì)一直等到馬上要使用對(duì)象實(shí)例 的時(shí)候才會(huì)被創(chuàng)建,懶人嘛披诗,總是推脫不開的時(shí)候才會(huì)真正執(zhí)行工作撬即,因此在裝載對(duì)象的時(shí)候不創(chuàng)建對(duì)象實(shí)例。
private static Singleton single=null;
懶漢式是典型的時(shí)間換空間,就是每次獲取實(shí)例都會(huì)進(jìn)行判斷呈队,看是否需要?jiǎng)?chuàng)建實(shí)例剥槐,浪費(fèi)判斷的時(shí)間。當(dāng)然宪摧,如果一直沒有人使用的話粒竖,那就不會(huì)創(chuàng)建實(shí)例,則節(jié)約內(nèi)存空間
由于懶漢式的實(shí)現(xiàn)是線程安全的几于,這樣會(huì)降低整個(gè)訪問的速度蕊苗,而且每次都要判斷。那么有沒有更好的方式實(shí)現(xiàn)呢沿彭?
3朽砰、雙重檢查加鎖
可以使用“雙重檢查加鎖”的方式來實(shí)現(xiàn),就可以達(dá)到實(shí)現(xiàn)線程安全,又能使性能不受很大影響瞧柔。
雙重檢查加鎖:并不是每次進(jìn)入getInstance()都需要同步漆弄,而是先不同步,進(jìn)入方法后造锅,先檢查單例對(duì)象是否存在撼唾,如果不存在才進(jìn)行下面的同步塊,這是第一重檢查备绽,進(jìn)入同步塊后券坞,再次檢查實(shí)例是否存在,如果不存在肺素,就在同步的情況下創(chuàng)建一個(gè)實(shí)例(單例對(duì)象),這是第二重檢查恨锚。這樣就只需要同步一次,從而減輕了多次在同步情況下進(jìn)行判斷所浪費(fèi)的時(shí)間倍靡。
“雙重檢查加鎖”機(jī)制的實(shí)現(xiàn)會(huì)使用關(guān)鍵字volatile猴伶,它的意思是:被volatile修飾的變量的值,將不會(huì)被本地線程緩存塌西,所有對(duì)該變量的讀寫都是直接操作共享內(nèi)存他挎,從而確保多個(gè)線程能正確的處理該變量。不清楚volatile的看過來volatile解析
代碼實(shí)例:
public class Singleton {
private volatile static Singleton instance = null;
private Singleton(){}
public static Singleton getInstance(){
//先檢查實(shí)例是否存在捡需,如果不存在才進(jìn)入下面的同步塊
if(instance == null){
//同步塊办桨,線程安全的創(chuàng)建實(shí)例
synchronized (Singleton.class) {
//再次檢查實(shí)例是否存在,如果不存在才真正的創(chuàng)建實(shí)例
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
這種實(shí)現(xiàn)方式既可以實(shí)現(xiàn)線程安全地創(chuàng)建實(shí)例站辉,而又不會(huì)對(duì)性能造成太大的影響呢撞。它只是第一次創(chuàng)建實(shí)例的時(shí)候同步,以后就不需要同步了饰剥,從而加快了運(yùn)行速度殊霞。
提示:由于volatile關(guān)鍵字可能會(huì)屏蔽掉虛擬機(jī)中一些必要的代碼優(yōu)化,所以運(yùn)行效率并不是很高汰蓉。因此一般建議绷蹲,沒有特別的需要,不要使用顾孽。也就是說祝钢,雖然可以使用“雙重檢查加鎖”機(jī)制來實(shí)現(xiàn)線程安全的單例,但并不建議大量采用若厚,可以根據(jù)情況來選用太颤。
根據(jù)上面的分析,常見的兩種單例實(shí)現(xiàn)方式都存在小小的缺陷盹沈,那么有沒有一種方案,既能實(shí)現(xiàn)延遲加載,又能實(shí)現(xiàn)線程安全呢乞封?那就是下面一種方法做裙,放大招了,接著呦肃晚。
4锚贱、靜態(tài)內(nèi)部類
public class Singleton {
private Singleton(){}
/**
* 類級(jí)的內(nèi)部類,也就是靜態(tài)的成員式內(nèi)部類关串,該內(nèi)部類的實(shí)例與外部類的實(shí)例
* 沒有綁定關(guān)系拧廊,而且只有被調(diào)用到時(shí)才會(huì)裝載,從而實(shí)現(xiàn)了延遲加載晋修。
*/
private static class SingletonHolder{
/**
* 靜態(tài)初始化器吧碾,由JVM來保證線程安全
*/
private static Singleton instance = new Singleton();
}
public static Singleton getInstance(){
return SingletonHolder.instance;
}
}
當(dāng)getInstance方法第一次被調(diào)用的時(shí)候,它第一次讀取SingletonHolder.instance墓卦,導(dǎo)致SingletonHolder類得到初始化倦春;而這個(gè)類在裝載并被初始化的時(shí)候,會(huì)初始化它的靜態(tài)域落剪,從而創(chuàng)建Singleton的實(shí)例睁本,由于是靜態(tài)的域,因此只會(huì)在虛擬機(jī)裝載類的時(shí)候初始化一次忠怖,并由虛擬機(jī)來保證它的線程安全性呢堰。
5、單例和枚舉
public enum Singleton {
/**
* 定義一個(gè)枚舉的元素凡泣,它就代表了Singleton的一個(gè)實(shí)例枉疼。
*/
uniqueInstance;
/**
* 單例可以有自己的操作
*/
public void singletonOperation(){
//功能處理
}
}
按照《高效Java 第二版》中的說法:?jiǎn)卧氐拿杜e類型已經(jīng)成為實(shí)現(xiàn)Singleton的最佳方法。用枚舉來實(shí)現(xiàn)單例非常簡(jiǎn)單问麸,只需要編寫一個(gè)包含單個(gè)元素的枚舉類型即可往衷。
對(duì)我來說,我比較喜歡第一種和第四種方式严卖,簡(jiǎn)單易懂席舍。而且在JVM層實(shí)現(xiàn)了線程安全(如果不是多個(gè)類加載器環(huán)境)。一般的情況下哮笆,我會(huì)使用第一種方式来颤,只有在要明確實(shí)現(xiàn)lazy loading效果時(shí)才會(huì)使用第四種方式
五、Android中典型的單例模式Application類
1稠肘、Application是什么福铅?
Application和Activity,Service一樣,是android框架的一個(gè)系統(tǒng)組件,當(dāng)android程序啟動(dòng)時(shí)系統(tǒng)會(huì)創(chuàng)建一個(gè) application對(duì)象项阴,用來存儲(chǔ)系統(tǒng)的一些信息滑黔。通常我們是不需要指定一個(gè)Application的,這時(shí)系統(tǒng)會(huì)自動(dòng)幫我們創(chuàng)建,如果需要?jiǎng)?chuàng)建自己 的Application略荡,也很簡(jiǎn)單創(chuàng)建一個(gè)類繼承 Application并在manifest的application標(biāo)簽中進(jìn)行注冊(cè)(只需要給Application標(biāo)簽增加個(gè)name屬性把自己的 Application的名字定入即可)庵佣。
android系統(tǒng)會(huì)為每個(gè)程序運(yùn)行時(shí)創(chuàng)建一個(gè)Application類的對(duì)象且僅創(chuàng)建一個(gè),所以Application可以說是單例 (singleton)模式的一個(gè)類.且application對(duì)象的生命周期是整個(gè)程序中最長(zhǎng)的汛兜,它的生命周期就等于這個(gè)程序的生命周期巴粪。因?yàn)樗侨?的單例的,所以在不同的Activity,Service中獲得的對(duì)象都是同一個(gè)對(duì)象粥谬。所以通過Application來進(jìn)行一些肛根,數(shù)據(jù)傳遞,數(shù)據(jù)共享 等,數(shù)據(jù)緩存等操作漏策。
2派哲、巧妙運(yùn)用單例模式特點(diǎn),通過Application來傳遞數(shù)據(jù)
假如有一個(gè)Activity A, 跳轉(zhuǎn)到 Activity B ,并需要推薦一些數(shù)據(jù)哟玷,通常的作法是Intent.putExtra() 讓Intent攜帶狮辽,或者有一個(gè)Bundle把信息加入Bundle讓Intent推薦Bundle對(duì)象,實(shí)現(xiàn)傳遞巢寡。但這樣作有一個(gè)問題在 于喉脖,Intent和Bundle所能攜帶的數(shù)據(jù)類型都是一些基本的數(shù)據(jù)類型,如果想實(shí)現(xiàn)復(fù)雜的數(shù)據(jù)傳遞就比較麻煩了抑月,通常需要實(shí)現(xiàn) Serializable或者Parcellable接口树叽。這其實(shí)是Android的一種IPC數(shù)據(jù)傳遞的方法。如果我們的兩個(gè)Activity在同一個(gè) 進(jìn)程當(dāng)中為什么還要這么麻煩呢谦絮,只要把需要傳遞的對(duì)象的引用傳遞過去就可以了题诵。
基本思路是這樣的。在Application中創(chuàng)建一個(gè)HashMap 层皱,以字符串為索引性锭,Object為value這樣我們的HashMap就可以存儲(chǔ)任何類型的對(duì)象了。在Activity A中把需要傳遞的對(duì)象放入這個(gè)HashMap叫胖,然后通過Intent或者其它途經(jīng)再把這索引的字符串傳遞給Activity B ,Activity B 就可以根據(jù)這個(gè)字符串在HashMap中取出這個(gè)對(duì)象了草冈。只要再向下轉(zhuǎn)個(gè)型 ,就實(shí)現(xiàn)了對(duì)象的傳遞瓮增。
六怎棱、總結(jié)
經(jīng)過網(wǎng)上的爬文終于了解了什么是單例模式,在這里感謝
http://www.cnblogs.com/java-my-life/archive/2012/03/31/2425631.html
http://blog.csdn.net/songylwq/article/details/6058771
http://www.blogjava.net/kenzhh/archive/2013/03/15/357824.html
http://www.cnblogs.com/hxsyl/archive/2013/03/19/2969489.html
http://blog.csdn.net/pi9nc/article/details/11200969