1. 程式人生 > >Java設計模式之單例模式及在Android中的重要使用

Java設計模式之單例模式及在Android中的重要使用

  之前在開發中老用到一些設計模式可是呢又不是很懂,於是狠下心來琢磨一番。下面是我琢磨後總結的,希望對您有用。如果發現了問題,請幫忙指正。

一、單例模式是什麼?

  單例模式最初的定義出現於《設計模式》:“保證一個類僅有一個例項,並提供一個訪問它的全域性訪問點。”
  Java中單例模式定義;“一個類有且僅有一個例項,並且自行例項化向整個系統提供該例項。”

二、為什麼用單例模式?

  對於系統中的某些類來說,只有一個例項很重要。例如,一個系統中可以存在多個列印任務,但是隻能有一個正在工作的任務;一個系統只有有一個視窗管理器或檔案系統;一個系統只能有一個計時工具或ID生成器。如在Windows OS 中就只能開啟一個工作管理員。如果不使用機制對視窗物件進行唯一化,將彈出多個視窗,如果這些視窗顯示的內容完全一致,則重複物件,浪費記憶體資源;如果這些視窗顯示的內容不一致,則意味著某一瞬間系統有多個狀態,與實際不符,也會為使用者帶來誤解,不知道哪一個才是真實的狀態。因此有時確保系統中某個物件的唯一性即一個類只能有一個例項是非常重要的。
  如何保證一個類只有一個例項並且這個例項易於被訪問呢?定義一個全域性變數可以確保物件隨時都可以被訪問,但不能防止我們例項化多個物件。一個更好的解決辦法是讓類自身負責儲存它的唯一例項。這個類可以保證沒有其他例項被建立,並且它可以提供一個訪問該例項的方法。這就是單例模式的模式動機。

三、單例模式特點

單例模式特點有三個
1、單例類只能有一個例項。
2、單例類必須自己建立自己的唯一例項。
3、單例類必須給其他物件(整個系統)提供這一例項。
 從具體實現角度分析,一是單例模式的類只提供私有的(private)建構函式,二是類定義中含有一個該類的靜態私有(private static)物件,三是該類提供了一個靜態的(static)公有的(public)函式用於建立或獲取它本身的靜態私有物件。

四、Java中幾種常見單例模式寫法

  通過上面的介紹你是不是對單例模式有了一個總的概念?沒有,那接下來繼續給你們放大招。
  基於單例模式特點,單例物件通常作為程式中存放配置資訊的載體(想想Android中的Application經常在裡面做一些配置的初始化),因為它能夠保證其他物件讀取到一致的資訊。例如在某個伺服器程式中,該伺服器的配置資訊可能存放在資料庫或 檔案中,這些配置資料由某個單例物件統一讀取,服務程序中的其他物件如果要獲取這些配置資訊,只需訪問該單例物件即可。這種方式極大地簡化了在複雜環境 下,尤其是多執行緒環境下的配置管理,但是隨著應用場景的不同,也可能帶來一些同步問題。

1、餓漢式單例

//餓漢式單例類.在類初始化時,已經自行例項化 
 public class Singleton {
     //私有的預設構造子
     private Singleton() {}
     //已經自行例項化 
     private static final Singleton single = new Singleton();
     //靜態工廠方法 
     public static Singleton getInstance() {
         return single;
     }
 }

  上面例子中,在這個類被載入時,靜態變數single會被初始化,此時類的私有構造子會被呼叫。這時單例類的唯一例項就被構造出來了。
  餓漢式其實是一種比較形象的稱謂。既然餓,那麼在建立物件例項的時候就比較著急,餓了嘛,於是在裝載類的時候就建立物件例項—>

private static final Singleton single = new Singleton();

  餓漢式是典型的空間換時間,當類裝載時就會建立類的例項,不管你用不用,先創建出來,然後每次呼叫的時候,就不需要再判斷,節省了執行時間。

2、懶漢式單例

//懶漢式單例類.在第一次呼叫的時候例項化自己   
public class Singleton {  
    private Singleton() {}  
    private static Singleton single=null;  
    //靜態工廠方法   
    public static synchronized Singleton getInstance() {  
         if (single == null) {    
             single = new Singleton();  
         }    
        return single;  
    }  
}  

  上面的懶漢式單例類實現裡對靜態工廠方法使用了同步化,以處理多執行緒環境。
  懶漢式其實是一種比較形象的稱謂。既然懶,那麼在建立物件例項的時候就不著急。會一直等到馬上要使用物件例項 的時候才會被建立,懶人嘛,總是推脫不開的時候才會真正執行工作,因此在裝載物件的時候不建立物件例項。

private static Singleton single=null;  

  懶漢式是典型的時間換空間,就是每次獲取例項都會進行判斷,看是否需要建立例項,浪費判斷的時間。當然,如果一直沒有人使用的話,那就不會建立例項,則節約記憶體空間
  由於懶漢式的實現是執行緒安全的,這樣會降低整個訪問的速度,而且每次都要判斷。那麼有沒有更好的方式實現呢?

3、雙重檢查加鎖

  可以使用“雙重檢查加鎖”的方式來實現,就可以達到實現執行緒安全,又能使效能不受很大影響。
  雙重檢查加鎖:並不是每次進入getInstance()都需要同步,而是先不同步,進入方法後,先檢查單例物件是否存在,如果不存在才進行下面的同步塊,這是第一重檢查,進入同步塊後,再次檢查例項是否存在,如果不存在,就在同步的情況下建立一個例項(單例物件),這是第二重檢查。這樣就只需要同步一次,從而減輕了多次在同步情況下進行判斷所浪費的時間。
  “雙重檢查加鎖”機制的實現會使用關鍵字volatile,它的意思是:被volatile修飾的變數的值,將不會被本地執行緒快取,所有對該變數的讀寫都是直接操作共享記憶體,從而確保多個執行緒能正確的處理該變數。不清楚volatile的看過來volatile解析
程式碼例項:

public class Singleton {
    private volatile static Singleton instance = null;
    private Singleton(){}
    public static Singleton getInstance(){
        //先檢查例項是否存在,如果不存在才進入下面的同步塊
        if(instance == null){
            //同步塊,執行緒安全的建立例項
            synchronized (Singleton.class) {
                //再次檢查例項是否存在,如果不存在才真正的建立例項
                if(instance == null){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

  這種實現方式既可以實現執行緒安全地建立例項,而又不會對效能造成太大的影響。它只是第一次建立例項的時候同步,以後就不需要同步了,從而加快了執行速度。
  提示:由於volatile關鍵字可能會遮蔽掉虛擬機器中一些必要的程式碼優化,所以執行效率並不是很高。因此一般建議,沒有特別的需要,不要使用。也就是說,雖然可以使用“雙重檢查加鎖”機制來實現執行緒安全的單例,但並不建議大量採用,可以根據情況來選用。
  根據上面的分析,常見的兩種單例實現方式都存在小小的缺陷,那麼有沒有一種方案,既能實現延遲載入,又能實現執行緒安全呢?那就是下面一種方法,放大招了,接著呦。

4、靜態內部類

public class Singleton {

    private Singleton(){}
    /**
     *    類級的內部類,也就是靜態的成員式內部類,該內部類的例項與外部類的例項
     *    沒有繫結關係,而且只有被呼叫到時才會裝載,從而實現了延遲載入。
     */
    private static class SingletonHolder{
        /**
         * 靜態初始化器,由JVM來保證執行緒安全
         */
        private static Singleton instance = new Singleton();
    }

    public static Singleton getInstance(){
        return SingletonHolder.instance;
    }
}

當getInstance方法第一次被呼叫的時候,它第一次讀取SingletonHolder.instance,導致SingletonHolder類得到初始化;而這個類在裝載並被初始化的時候,會初始化它的靜態域,從而建立Singleton的例項,由於是靜態的域,因此只會在虛擬機器裝載類的時候初始化一次,並由虛擬機器來保證它的執行緒安全性。

5、單例和列舉

public enum Singleton {
    /**
     * 定義一個列舉的元素,它就代表了Singleton的一個例項。
     */

    uniqueInstance;

    /**
     * 單例可以有自己的操作
     */
    public void singletonOperation(){
        //功能處理
    }
}

按照《高效Java 第二版》中的說法:單元素的列舉型別已經成為實現Singleton的最佳方法。用列舉來實現單例非常簡單,只需要編寫一個包含單個元素的列舉型別即可。

  對我來說,我比較喜歡第一種和第四種方式,簡單易懂。而且在JVM層實現了執行緒安全(如果不是多個類載入器環境)。一般的情況下,我會使用第一種方式,只有在要明確實現lazy loading效果時才會使用第四種方式

五、Android中典型的單例模式Application類

1、Application是什麼?

  Application和Activity,Service一樣,是android框架的一個系統元件,當android程式啟動時系統會建立一個 application物件,用來儲存系統的一些資訊。通常我們是不需要指定一個Application的,這時系統會自動幫我們建立,如果需要建立自己 的Application,也很簡單建立一個類繼承 Application並在manifest的application標籤中進行註冊(只需要給Application標籤增加個name屬性把自己的 Application的名字定入即可)。
  android系統會為每個程式執行時建立一個Application類的物件且僅建立一個,所以Application可以說是單例 (singleton)模式的一個類.且application物件的生命週期是整個程式中最長的,它的生命週期就等於這個程式的生命週期。因為它是全域性 的單例的,所以在不同的Activity,Service中獲得的物件都是同一個物件。所以通過Application來進行一些,資料傳遞,資料共享 等,資料快取等操作。

2、巧妙運用單例模式特點,通過Application來傳遞資料

  假如有一個Activity A, 跳轉到 Activity B ,並需要推薦一些資料,通常的作法是Intent.putExtra() 讓Intent攜帶,或者有一個Bundle把資訊加入Bundle讓Intent推薦Bundle物件,實現傳遞。但這樣作有一個問題在 於,Intent和Bundle所能攜帶的資料型別都是一些基本的資料型別,如果想實現複雜的資料傳遞就比較麻煩了,通常需要實現 Serializable或者Parcellable介面。這其實是Android的一種IPC資料傳遞的方法。如果我們的兩個Activity在同一個 程序當中為什麼還要這麼麻煩呢,只要把需要傳遞的物件的引用傳遞過去就可以了。
基本思路是這樣的。在Application中建立一個HashMap ,以字串為索引,Object為value這樣我們的HashMap就可以儲存任何型別的物件了。在Activity A中把需要傳遞的物件放入這個HashMap,然後通過Intent或者其它途經再把這索引的字串傳遞給Activity B ,Activity B 就可以根據這個字串在HashMap中取出這個物件了。只要再向下轉個型 ,就實現了物件的傳遞。

六、總結