《Android源碼設計模式解析與實戰》讀書筆記(二) 《Android源碼設計模式解析與實戰》PDF資料下載php
單例模式是應用最廣的模式之一。在應用這個模式時,單例對象的類必須保證只有一個實例存在。不少時候整個系統只須要擁有一個全局對象,這樣有利於咱們協調系統總體的行爲。設計模式
確保某一個類只有一個實例,並且自行實例化並向整個系統提供這個實例。安全
確保某個類有且只有一個對象的場景,避免產生多個對象消耗過多的資源,或者某種類型的對象只應該有且只有一個。bash
實現單例模式主要有以下幾個關鍵點:微信
經過將單例類的構造函數私有化,使得不能經過new的形式手動構造單例類的對象。單例類會暴露一個公有靜態方法,須要調用這個靜態方法獲取到單例類的惟一對象,在獲取這個單例對象的和過程當中須要確保線程安全,即在多線程環境下構造單例類的對象也是有且只有一個,這也是單例模式實現中比較困難的地方。多線程
單例模式是設計模式中比較簡單的,只有一個單例類,沒有其餘的層次結構與抽象。併發
該模式須要確保該類只能生成一個對象,一般是該類須要消耗較多的資源或者沒有多個實例的狀況。ide
請看下面示例。 一個公司能夠有幾個VP、無數個員工,可是CEO只有一個。函數
//普通員工
public class Staff {
public void work(){
//工做
}
}
複製代碼
//副總裁
public class VP extends Staff{
@Override
public void work() {
//管理下面的經理
}
}
複製代碼
//CEO,餓漢式單例模式
public class CEO extends Staff {
private static final CEO mCeo = new CEO();
//構造函數私有
public CEO() {
}
//公有的靜態函數,對外暴露獲取單例對象的接口
public static CEO getCeo() {
return mCeo;
}
@Override
public void work() {
//管理VP
}
}
複製代碼
//公司類
public class Company {
private List<Staff> allStaffs=new ArrayList<Staff>();
public void addStaff(Staff per) {
allStaffs.add(per);
}
public void showAllStaffs(){
for (Staff per :
allStaffs) {
Log.e("TAG","Obj:" + per.toString());
}
}
}
複製代碼
private void init() {
Company cp=new Company();
//CEO對象只能經過getCeo函數獲取
Staff ceo1 = CEO.getCeo();
Staff ceo2 = CEO.getCeo();
cp.addStaff(ceo1);
cp.addStaff(ceo2);
//經過new建立VP對象
Staff vp1 = new VP();
Staff vp2 = new VP();
//經過new建立Staff對象
Staff mStaff1 = new Staff();
Staff mStaff2 = new Staff();
Staff mStaff3 = new Staff();
cp.addStaff(vp1);
cp.addStaff(vp2);
cp.addStaff(mStaff1);
cp.addStaff(mStaff2);
cp.addStaff(mStaff3);
cp.showAllStaffs();
}
複製代碼
輸出結果以下:高併發
2019-01-07 23:13:26.925 5313-5313/? E/TAG: Obj:com.tengxin.mytest.CEO@4c4ca4b
2019-01-07 23:13:26.926 5313-5313/? E/TAG: Obj:com.tengxin.mytest.VP@e6f2c28
2019-01-07 23:13:26.926 5313-5313/? E/TAG: Obj:com.tengxin.mytest.VP@3ba2d41
2019-01-07 23:13:26.926 5313-5313/? E/TAG: Obj:com.tengxin.mytest.Staff@15542e6
2019-01-07 23:13:26.926 5313-5313/? E/TAG: Obj:com.tengxin.mytest.Staff@f93c027
2019-01-07 23:13:26.926 5313-5313/? E/TAG: Obj:com.tengxin.mytest.Staff@b9ee2d4
複製代碼
從代碼中能夠看到,CEO類不能經過new的形式構造對象,只能經過CEO.getCEO()函數來獲取。 這個實現的核心在於量CEO類的構造方法進行了私有化,使得外部程序不能經過構造函數來構造CEO對象,而CEO類經過一個靜態方法返回一個靜態對象。
####3.一、懶漢式單例模式
懶漢式單例模式是聲明一個靜態對象,而且在用戶第一次調用getInstance時進行初始化;而餓漢式單例模式是在聲明靜態對象時就已經初始化。
懶漢式單例模式實現以下:
//懶漢式單例模式
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
複製代碼
getInstance()方法中添加了synchronized關鍵字,使得在多線程狀況下保證單例對象惟一。
可是懶漢式單例模式也存在一個問題,就是每次調用getInstance方法都會進行同步,這樣會消耗沒必要要的資源。
懶漢式單例模式總結
DCL方式實現單例模式的優勢是既可以在須要時初始化單例,又可以保證線程安全,且單例對象初始化後調用getInstance不進行同步鎖。
代碼以下:
//DCL方式實現單例模式
public class DCLSingleton {
private static DCLSingleton mSingleton = null;
public DCLSingleton() {}
public void doSomething(){
Log.e("TAG", "do sth.");
}
public static DCLSingleton getInstance(){
if (mSingleton == null) {
synchronized (DCLSingleton.class) {
if (mSingleton == null) {
mSingleton = new DCLSingleton();
}
}
}
return mSingleton;
}
}
複製代碼
這種方式的亮點就在getInstance方法上,getInstance方法對mSingleton進行了兩次判空:
DCL總結:
DCL雖然在必定程度上解決了資源消耗、多餘的同步、線程安全等問題,可是,它仍是在某些狀況下出現失效的問題。這個問題被稱爲雙重檢查鎖定(DCL)失效。
建議使用以下代碼:
//靜態內部類單例模式
public class Singleton {
private Singleton() {}
public static Singleton getInstance(){
return SingletonHolder.sInstance;
}
/**
* 靜態內部類
*/
private static class SingletonHolder{
private static final Singleton sInstance =new Singleton();
}
}
複製代碼
當第一次加載Singleton類時並不會初始化sInstance,只有在第一次調用Singleton的getInstance方法時纔會致使sInstance被初始化。
這種方式不只可以確保線程安全,也可以保證單例對象的惟一性,同時也延遲了單例的實例化,這是推薦使用的單例模式實現方式。
//枚舉單例
public enum SingletonEnum {
INSTANCE;
public void doSomething(){
Log.e("TAG", "doSomething " );
}
}
複製代碼
枚舉單例最大的優勢就是寫法簡單,枚舉在Java中與普通的類是同樣的,不只可以有字段,還可以有本身的方法。最重要的是默認枚舉實例的建立是線程安全的,而且字啊任何狀況下它都是一個單例。
經過序列化能夠將一個單例的實例對象寫到磁盤,而後再讀回來,從而有效地得到一個實例。即便構造函數是私有的,反序列化時依然能夠經過特殊的途徑去建立類的一個新的實例,至關於調用該類的構造函數。反序列化操做提供了一個很特別的鉤子函數,類中具備一個私有的、被實例化的方法readResolve(),這個方法能夠控制對象的反序列化。上述幾個示例中要杜絕單例對象在被反序列化時從新生成對象,必須加入以下方法:
private Object readResolve() throws ObjectStreamException {
return mSingleton;
}
複製代碼
而對於枚舉,並不存在這個問題,由於即便反序列化也不會從新生成新的實例。
下面是一種另類的實現:
//使用容器實現單例模式
public class SingletonManager {
private static Map<String, Object> objMap = new HashMap<>();
private SingletonManager() {}
public static void registerService(String key,Object instance){
if (!objMap.containsKey(key)) {
objMap.put(key, instance);
}
}
public static Object getService(String key) {
return objMap.get(key);
}
}
複製代碼
這種方式能夠管理多種類型的單例,而且在使用時能夠經過統一的接口進行獲取操做,下降了用戶的使用成本,也對用戶隱藏了具體實現,下降了耦合度。
不管什麼形式實現單例模式,其核心原理都是將構造函數私有化,而且經過靜態方法獲取一個惟一的實例,在獲取的過程當中要保證線程安全、防止反序列化致使從新生成實例對象等問題。
單例模式是運用頻率很高的模式,在客戶端一般沒有高併發的狀況下,選擇哪一種實現方式並不會有太大的影響。出於效率考慮,推薦使用DCL形式單例模式、靜態內部類單例模式。