1. 程式人生 > >Android中的單例模式

Android中的單例模式

單例模式在設計模式中是最方便人們理解的。雖然容易理解,但裡面坑挺多的,所以在面試題中經常哪來做考題。簡單來說,單例模式,就是為了保證類物件只有並且只能建立一個。規模比較大的類,或者需要反覆使用的類,才會使用單例模式。下面一起探討一下單例模式的幾種方式。

懶漢式,執行緒不安全

當提到單例模式的時候,可能很多人會在第一時間想到如下的程式碼。
    public class Singleton {
        private static Singleton instance;
        private Singleton (){}

        public static Singleton getInstance
()
{
if (instance == null) { instance = new Singleton(); } return instance; } }
在這個方法中,其實程式碼非常的簡單明瞭,但是其存在的問題就是,當多個執行緒並行呼叫上面的getInstance()時,就會建立多個例項,結果就是不能在多執行緒下正常執行。

懶漢式,執行緒安全

    為了解決上述的問題,其實很簡單,只需要在getInstance()加一個同步鎖(synchronized)就可以了.
public
static synchronized Singleton getInstance()
{
if (instance == null) { instance = new Singleton(); } return instance; }
    如果多個執行緒進入,只允許進入一個,且執行完之後,才能允許另一個執行緒進入。

雙重檢驗鎖

    雙重檢驗鎖,是一種使用同步塊加鎖的方法。因為會有兩次if判斷檢查instance是否為空,一次是在同步塊外,一次是在同步塊內。
    public static
Singleton getSingleton()
{
if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance ; }
    這段程式碼看起來很完美,不過有問題,主要是instance = newSingleton()這句,這並非一個原子操作,事實上在JVM中這句話大概做了下面三件事情。     1.給instance分配記憶體     2.呼叫Singleton的建構函式來初始化成員變數     3.將instance物件指向分配的記憶體空間(執行完這一步instance就非null了)     但是在JVM的即時編譯器中存在指令重排序的優化。也就是說上面的第二步和第三部的順序是不能保證的,最終的執行順序可能是1-2-3,也可能是1-3-2.如果是後者,則在3執行完畢、2未執行之前,被執行緒二搶佔了。這時instance已經是非null了(但卻沒有初始化),所以執行緒二會直接返回instance,然後使用,然後就GG報錯了。  我們只需要把instance變數宣告成volatile就可以了。          
    public class Singleton {
        private volatile static Singleton instance; //宣告成 volatile
        private Singleton (){}

        public static Singleton getSingleton() {
            if (instance == null) {                         
                synchronized (Singleton.class) {
                    if (instance == null) {       
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }

    }
在多執行緒併發中synchronized和Volatile都扮演著重要的角色,Volatile是輕量級的synchronized,它在多處理器開發中保證了共享變數的“可見 性”。可見性的意思是當一個執行緒修改一個共享變數時,另外一個執行緒能讀到這個修改的值。它在某些情況下比synchronized的開銷更小。

餓漢式 static final field

這種方法很簡單,因為單利的例項被宣告稱static和final變量了,在第一次載入類到記憶體中時就會初始化,所以建立例項本身是執行緒安全的。  
    public class Singleton{
        //類載入時就初始化
        private static final Singleton instance = new Singleton();

        private Singleton(){}

        public static Singleton getInstance(){
            return instance;
        }
    }
這種寫法缺點是它不是一種懶漢式載入模式,單例會在載入類後一開始就被初始化,即時客戶端沒有呼叫getInstance()方法,餓漢式的建立方式在 一些場景中無法使用:譬如Singleton例項的建立時依賴引數或者配置檔案的,在getInstance()之前必須呼叫某個方法設定引數給它,那樣這種單例寫 法就無法使用了。

靜態內部類

個人傾向於靜態內部類的方法。
    public class Singleton {  
        private static class SingletonHolder {  
            private static final Singleton INSTANCE = new Singleton();  
        }  
        private Singleton (){}  
        public static final Singleton getInstance() {  
            return SingletonHolder.INSTANCE; 
        }  
    }
這種寫法仍然使用JVM本身機制保證了執行緒安全問題;由於SingletonHolder是私有的,除了getInstance之外沒有辦法訪問它,因為它是懶漢式的; 同時讀取例項的時候不會進行同步,沒有效能缺陷;也不依賴JDK版本。

列舉

這種方法很簡單,也是它最大的優點了。
    public enum EasySingleton{
        INSTANCE;
    }
通過EasySingleton.INSTANCE來訪問例項,比呼叫getInsance()方法方便多了。建立列舉預設執行緒是安全的,所以不必擔心double checked locki -ng而且還能防止反序列化導致重新建立新的物件。這種方法很少看到有人寫,大概是因為不熟悉。

總結

一般來說,單例模式一般分為5種寫法:懶漢式、餓漢式、雙重檢驗鎖、靜態內部類、列舉。上述所說多事執行緒安全的實現,開頭中的第一種方法不 算正確的寫法。正常情況下沒直接使用餓漢式就好了,如果明確要求要懶漢式會傾向於靜態內部類,如果涉及到反序列化建立物件時會試著使用列舉方 來實現單例。