### 前言
面向對象語言具有封裝,繼承,多態等三個特性,同時,面向對象語言通常有好多種設計模式,這些設計模式在面向對象語言中是相通的,java是一種面向對象的語言,使用java語言作為開發語言的Android的源碼中大量運用了設計模式。單例模式是應用最為廣泛而且最為簡單的一種設計模式。
單例模式:在任何時刻保證單例對象只存在一個實例,這個實例對象服務于整個系統,如:一個公司只有一個CEO,一個國家只有一個主席或者總統,一個宇宙只有一個地球。同時J2EE中的Application,Session及安卓中的Application都是單例。
### 實現單例模式的幾種方式
實現單例模式需要注意一下幾個關鍵點即可:
1. 構造函數私有化,即權限設置為private
1. 通過一個靜態方法或者枚舉返回單例類對象
1. 確保單例類的實例只有一個,尤其是在多線程的情況下
1. 確保單例類對象在反序列化是不會重新創建對象
通過將單例類的構造函數私有化,使得客戶端代碼不同通過new產生該類的實例。該單例類通過暴露一個方法給客戶端,使得客戶端通過該方法得到該單例類的唯一實例,通過還需要在獲取該類的實例的過程中保證線程安全,即在多線程的情況下也不能出現單例類多個實例的情況,即單例類的實例在任何情況下都有且只有一個,并向整個系統提供使用。
以java語言為例,實現單例的方式有好多種:
1、懶漢模式(簡單但不建議使用)
~~~
public class Singleton {
public static Singleton mInstance;
/**
* 構造函數的私有化
*/
private Singleton (){
}
/**
* 提供一個靜態方法獲取單例類的實例
* @return
*/
public static synchronized Singleton getInstance(){
if(mInstance == null){
mInstance = new Singleton();
}
return mInstance;
}
}
~~~
這種方式簡單,但是每次得到實例對象的時候都要同步,同步的過程是需要開銷的,所以效率低,不推薦使用。
2、Double Check Lock(DCL)(一般可以考慮使用) –>改進的懶漢模式
~~~
public class Singleton {
public static Singleton mInstance;
/**
* 構造函數的私有化
*/
private Singleton(){
}
/**
* 提供一個靜態方法獲取單例類的實例
* @return
*/
public static Singleton getInstance(){
if(mInstance == null){ // 第一次檢查
synchronized (Singleton.class) { // lock
if(mInstance == null){ // 第二次檢查
mInstance = new Singleton();
}
}
}
return mInstance;
}
}
~~~
這個方式對懶漢模式做了一些改進,synchronized沒有加在方法上,而是加在了方法體上。調用該方法的時候先檢查實例對象是否為空,不為空直接放返回,為空就加同步(防止多線程出現多個實例的情況),并判斷實例對象是否為空,為空就創建。這種方式只會在實例對象第一次為空的情況下同步。克服了懶漢模式每次請求得到實例都有同步,造成不必要的開銷的缺點。
注意:該方法由于java內存模型的原因偶爾會失敗,但是在絕大多數情況下可以保證單例對象的唯一性。
> java的內存模型,在通過new新建一個實例的時候,這個新建語句最終會編譯成多條匯編指令:
(1) 給Singleton的實例分配內存
(2) 調用Singleton()構造函數,初始化成員屬性
(3) 將該實例的引用mInstance 指向分配的內存空間
3、靜態內部類實現單例(推薦)
~~~
public class Singleton {
/**
* 構造方法私有化
*/
private Singleton(){
}
/**
* 私有的內部靜態類,使用了加載外部類的時候內部類不會立即加載的特性
* @author lt
*
*/
private static class SingletonHolder{
public static Singleton mInstance = new Singleton();
}
/**
* 暴露一個方法取得單例對象的實例
* @return
*/
public static Singleton getInstance(){
return SingletonHolder.mInstance;
}
}
~~~
當第一次加載Singleton類的時候,并不會初始化mInstance,只有在調用了Singleton的getInstance()方法時候才會初始化mInstance,第一次調用getInstance()方法會導致虛擬機加載其內部類,這種方式不僅可以確保線程安全,也能夠保證單例對象的唯一性,同時也延遲單例對象的初始化。推薦使用
4、枚舉單例(超級簡單)
~~~
public enum Singleton {
HELLO;
public void println(String text){
System.out.println(text);
}
}
~~~
枚舉與java中的普通類一樣,不僅可以有字段,也可以有自己的方法,如果我們需要一個對象來實現一個簡單的方法能完成的事件,如:Android中顯示土司,那么我們就可以考慮使用枚舉,并且枚舉實例的創建默認是**線程安全**的,其他的類盡管將類的構造方法私有化了,但反序列化是會重新生成實例,而枚舉不會。
5、使用容器實現單例模式
~~~
public class Singleton {
private static Map<String,Object> instances = new HashMap<String,Object>();
public static void putObj(String key,Object instance){
if(!instances.containsKey(key)){
instances.put(key, instance);
}
}
public static Object getInstance(String key){
return instances.get(key);
}
}
~~~
該方法就是將已經產生的對象保存到HashMap中暫存起來,每次根據相同的key從這里取到同一個實例。通常在程序開始的時候會將多種類的實例保存到該集合中,這樣可以管理這些類的實例,下次直接取出這個實例。
總結:單例模式作為面向對象設計模式中應用最簡單最廣泛的一種設計模式。這種設計模式帶來的好處是:
(1)提高了系統的性能,如:一個包含線程池,緩存系統,網絡請求的ImageLoader對象,因為這種類型的對象占用的系統資源大,單例模式解決了這個缺點,整個系統中只有一個該對象的實例,減小了系統性能開銷。
(2)減小了系統內存開銷,單例模式使得某類對象在內存中只存在一個實例,從而減少了系統內存的占用,
(3)單例模式避免對資源的多重占用保證系統整體的協調,試想一下,如果一個對文件的寫操作,如果只有一個對象存在內存中,那么避免了對文件同時寫的過程
(4)單例對象可以設置全局的通訊站點,優化和共享資源訪問,如:Android中的Application,我們可以在Application中放置一些數據,從而在其他的地方都可以取出該數據,實現數據全局共享。
單例模式的缺點:
(1)單例對象由一般沒有接口,也不是抽象的不易擴展,如要擴展,就需要修改類的源代碼,這點和開閉原則相違背。
(2)單例對象的使用容易產生一些問題,如:我們在單例對象放置了某些因生命周期或者其它原因我們人為無法控制而失效,那么我們在其它地方使用就會出現內存泄漏等錯誤。