一直想整理一下關(guān)于andorid設(shè)計(jì)模式的文章,也方便自己需要的時(shí)候回顧下,由于有些地方也沒弄明白雇盖,所以有些地方可能理解的不對,對于這些歡迎大家指出栖忠,共同討論一起進(jìn)步崔挖,@—@ 哈哈!
單例模式:
單例模式應(yīng)該是設(shè)計(jì)模式中最容易理解庵寞,也是最容易手寫代碼的狸相,大家平時(shí)寫代碼中也應(yīng)該經(jīng)常使用到,那究竟什么是單例了? 我的理解就是在一個(gè)類中只實(shí)例化一個(gè)實(shí)例捐川,并向整個(gè)系統(tǒng)提供一個(gè)節(jié)點(diǎn)使用該個(gè)實(shí)例脓鹃。那什么時(shí)候應(yīng)該去使用單例模式了?通常來說只有是在該類需要進(jìn)行大量耗資源操作或者只需要?jiǎng)?chuàng)建一個(gè)實(shí)例的情況下使用属拾,例如一個(gè)班級只有一個(gè)班長将谊,一個(gè)國家只有一個(gè)主席等等。而在android系統(tǒng)中也有很多地方用到單例模式渐白,我們經(jīng)常會(huì)通過Context獲取系統(tǒng)級別的服務(wù)尊浓,比如WindowsManagerService, ActivityManagerService等,更常用的是一個(gè)叫LayoutInflater的類, 這些類以單例的形式注冊在系統(tǒng)中纯衍,我們需要的時(shí)候就通過Context的getSystemService(String key)獲取栋齿。
既然說了什么是單例,那如何使用了?單例的寫法在java中有很多瓦堵,接下來回列舉一些常用的寫法基协。
1.懶漢式(就是在需要調(diào)用時(shí)候去加載)
public class SingleInstance {
private static SingleInstance mInstance;
/**
* 創(chuàng)建私有構(gòu)造器
*/
private SingleInstance() {
}
public static SingleInstance getInstance() {
if (mInstance == null) {
mInstance = new SingleInstance();
}
return mInstance;
}}
這個(gè)應(yīng)該是最常見的一種寫法,很多書上視頻上都是這樣寫的菇用,但是這種寫法在多線程的情況下會(huì)實(shí)例化多個(gè)實(shí)例澜驮,因此這個(gè)是線程不安全的。所以為了保證線程安全可以加上關(guān)鍵字synchronized
public class SingleInstance {
private static SingleInstance mInstance;
/**
* 創(chuàng)建私有構(gòu)造器
*/
private SingleInstance() {
}
public static synchronized SingleInstance getInstance() {
if (mInstance == null) {
mInstance = new SingleInstance();
}
return mInstance;
}}
加上synchronized的關(guān)鍵字可以解決實(shí)例化多個(gè)實(shí)例的問題惋鸥,但方法前加這個(gè)效率并不高杂穷,每次只能用一個(gè)線程訪問,所以可以在上面的基礎(chǔ)上這樣修改卦绣。
2.雙重檢驗(yàn)鎖模式
public class SingleInstance {
private static SingleInstance mInstance;
/**
* 創(chuàng)建私有構(gòu)造器
*/
private SingleInstance() {
}
public static SingleInstance getInstance() {
if (mInstance == null) {
synchronized (SingleInstance.class) {
if(mInstance == null){
mInstance = new SingleInstance();
}
}
}
return mInstance;
}}
這種寫法就是雙重檢驗(yàn)鎖模式耐量,進(jìn)行兩次檢驗(yàn)mInstance的操作,一次是同步前滤港,一次是同步后廊蜒,防止多次實(shí)例化。但是這種方式任然存在問題的可能溅漾,它是有問題山叮,主要在于mInstance= new SingleInstance()這句,這并非是一個(gè)原子操作樟凄,事實(shí)上在 JVM 中這句話大概做了下面 3 件事情聘芜。
1.給 mInstance分配內(nèi)存
2.調(diào)用 SingleInstance的構(gòu)造函數(shù)來初始化成員變量
3.將mInstance對象指向分配的內(nèi)存空間.
但是在 JVM 的即時(shí)編譯器中存在指令重排序的優(yōu)化。也就是說上面的第二步和第三步的順序是不能保證的缝龄,最終的執(zhí)行順序可能是 1-2-3 也可能是 1-3-2汰现。如果是后者,則在 3 執(zhí)行完畢叔壤、2 未執(zhí)行之前瞎饲,被線程二搶占了,這時(shí) mInstance已經(jīng)是非 null 了(但卻沒有初始化)炼绘,所以線程二會(huì)直接返回 mInstance,此時(shí)就會(huì)出錯(cuò)了嗅战。那對于上面這個(gè)問題我們只需要將 mInstance變量聲明成 volatile 就可以了。java 1.5后對其進(jìn)行了修改俺亮,當(dāng)引入volatile時(shí) 具備以下幾點(diǎn):
1)保證了不同線程對這個(gè)變量進(jìn)行操作時(shí)的可見性驮捍,即一個(gè)線程修改了某個(gè)變量的值,這新值對其他線程來說是立即可見的脚曾。
2)禁止進(jìn)行指令重排序东且。
volatile關(guān)鍵字禁止指令重排序有兩層意思:
1)當(dāng)程序執(zhí)行到volatile變量的讀操作或者寫操作時(shí),在其前面的操作的更改肯定全部已經(jīng)進(jìn)行本讥,且結(jié)果已經(jīng)對后面的操作可見珊泳;在其后面的操作肯定還沒有進(jìn)行鲁冯;
2)在進(jìn)行指令優(yōu)化時(shí),不能將在對volatile變量訪問的語句放在其后面執(zhí)行色查,也不能把volatile變量后面的語句放到其前面執(zhí)行薯演。
這樣就不會(huì)存在無序?qū)懭耄敲碨ingleInstace對象不會(huì)存在返回一個(gè)沒有實(shí)例話的對象秧了,只有實(shí)話成功后才會(huì)將該地址指向?qū)ο蟆?br>
具體vollatile可以參見這篇文章:Volatile跨扮。
3.餓漢式 static final field
這種方式是直接在類加載到內(nèi)存時(shí)去初始化的,同時(shí)聲明為final和static所以創(chuàng)建實(shí)例本身是線程安全的示惊。
public class SingleInstance {
//類加載時(shí)就初始化
private static final SingleInstance mInstance= new SingleInstance ();
private SingleInstance (){}
public static SingleInstance getInstance(){
return mInstance;
}}
4.靜態(tài)內(nèi)部類 static nested class
public class SingleInstance {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private SingleInstance (){}
public final static SingleInstance getInstance(){
return mInstance;
}}
因?yàn)橥瑯有揎椓薴inal好港,static ,同時(shí)SingletonHolder是靜態(tài)內(nèi)部類米罚,只有g(shù)etInstance調(diào)用時(shí)才會(huì)加載,所以也是惰性初始化(懶加載)丈探。
5.最后一種用的很少的枚舉
用枚舉寫單例實(shí)在太簡單了录择!這也是它最大的優(yōu)點(diǎn)。下面這段代碼就是聲明枚舉實(shí)例的通常做法碗降。
public enum SingleInstance {
//枚舉元素
INSTANCE;}
而枚舉單例應(yīng)該是代碼最簡單的實(shí)現(xiàn)方式了隘竭,同時(shí)一般單例存在的另外一個(gè)問題是一旦你實(shí)現(xiàn)了序列化接口,那么它們不再保持單例了讼渊,因?yàn)閞eadObject()方法一直返回一個(gè)新的對象就动看,而枚舉你可以通過使用readResolve()方法來避免。
public enum SingleInstance {
//枚舉元素
private Object readResolve(){
return INSTANCE;
}
}
這些就是常用的單例實(shí)現(xiàn)方式爪幻,那么是不是保證單例的安全性和效率是不是就不會(huì)有問題了菱皆,不是由于單例模式對象是靜態(tài)的所以其生命周期比較長,容易造成內(nèi)存泄漏挨稿。特別是傳遞Context 對象時(shí)特別注意:
public class SingleInstance {
private static SingleInstance mInstance;
private Context mcContext;
/**
* 創(chuàng)建私有構(gòu)造器
*/
private SingleInstance(Context c) {
mcContext = c;
}
public static SingleInstance getInstance(Context c) {
if (mInstance == null) {
synchronized (SingleInstance.class) {
if (mInstance == null) {
mInstance = new SingleInstance(c);
}
}
}
return mInstance;
}}
如果傳遞的是一個(gè)activity的句柄時(shí)仇轻,如果該activity退出,系統(tǒng)GC的時(shí)候試圖去回收奶甘,但它的句柄被該單例對象引用篷店,GC回收它失敗,就會(huì)造成內(nèi)存泄漏臭家。所以單例的時(shí)候如果引入Context對象時(shí)疲陕,最好用弱引用或者private WeakReference<Context> c,或者是調(diào)用ApplicationContext對象其生命周期伴隨整個(gè)應(yīng)用钉赁。關(guān)于單例一般都是以上所說蹄殃,如果大家有更多的了解歡迎補(bǔ)充¢厦梗總的來說單例在某些情況下可控制實(shí)例產(chǎn)生的數(shù)量窃爷,達(dá)到節(jié)約資源的目的邑蒋,同時(shí)也可進(jìn)行數(shù)據(jù)媒介共享,它可以在不建立直接關(guān)聯(lián)的條件下按厘,讓多個(gè)不相關(guān)的兩個(gè)線程或者進(jìn)程之間實(shí)現(xiàn)通信医吊。但單例不足之處就是單利模式中沒有抽象層,因此單例類的擴(kuò)展有很大的困難逮京。