java單例模式

爲什麼需要單例模式

有時候我們需要使用一個實用類A,這個類A專門提供一些公共功能供別人調用,而本身並不會處理業務邏輯。由於類A會被許多類乃至線程調用,假設我們的程序非常龐大,在運行的過程中,會訪問這個類A100次,爲了調用類A的方法,需要先創建A的對象,A a = new A()。這種方法在對A的訪問量較少的情況下沒問題,但是像我們這種情況,就會創建100個類A的實例,這100個實例是要佔用內存的,從這種角度來說,就造成了大量不必要的開銷。而單例模式,在整個程序生命週期中,只有一個實例,這樣就不會造成不必要的內存消耗。

單例模式的設計

爲了讓整個生命週期內只有一個實例,我們可以這樣做:
 
  1. public class Singleton {  
  2.   
  3.     private static Singleton sSingleton;  
  4.   
  5.     private Singleton() {  
  6.     }  
  7.   
  8.     public static Singleton getInstance() {  
  9.         if (sSingleton == null) {  
  10.             sSingleton = new Singleton();  // line A   
  11.         }  
  12.   
  13.         return sSingleton;  
  14.     }  
  15.   
  16. }  
public class Singleton {

    private static Singleton sSingleton;

    private Singleton() {
    }

    public static Singleton getInstance() {
        if (sSingleton == null) {
            sSingleton = new Singleton();  // line A
        }

        return sSingleton;
    }

}
上述做法好像沒啥問題,由於mSingleton是靜態的,因此能夠保證程序運行過程中只存在一個實例。但是針對多線程情況,就可能有問題,比如有2個線程同時併發調用getInstance方法,並且同時執行到了line A,這個時候還是會各自new一個對象出來,也就是說,存在了兩個實例,這違背了單例模式的概念,下面我們改進一下:
  1. public class Singleton {  
  2.   
  3.     private static Singleton sSingleton;  
  4.   
  5.     private Singleton() {  
  6.     }  
  7.   
  8.     public static Singleton getInstance() {  
  9.         synchronized (Singleton.class) {  
  10.   
  11.             if (mSingleton == null) {  
  12.                 sSingleton = new Singleton();  
  13.             }  
  14.             return sSingleton;  
  15.         }  
  16.   
  17.     }  
  18.   
  19. }  
public class Singleton {

    private static Singleton sSingleton;

    private Singleton() {
    }

    public static Singleton getInstance() {
        synchronized (Singleton.class) {

            if (mSingleton == null) {
                sSingleton = new Singleton();
            }
            return sSingleton;
        }

    }

}
上述做法的確是沒啥問題了,getInstance方法中對Singleton.class加鎖,可以保證同一時刻只有一個線程能夠進入getInstance方法。現在考慮一種情況,還是我們的比較龐大的工程,在某個變態的時刻,我們需要訪問Singleton對象100次,注意是高併發下的同時訪問,會是什麼情形呢?大概是這樣的:100個線程進入getInstance方法以後開始搶mSingleton的所有權,這個時候,有一個線程獲得了鎖,然後順利地得到了Singleton實例,接着會是什麼情形呢?應該是這樣的:剩下99個線程開始搶mSingleton的所有權,一直這樣類推下去,可能有一個線程運氣比較差,搶了100次才搶到鎖,程序的表現可能是這樣的:這個運氣差的線程被阻塞在getInstance方法中,遲遲無法返回,如果需要返回數據給ui的話,那麼ui將遲遲不會得到更新。

我們需要看一下上述代碼,真的需要每次進入getInstance方法都要獲得鎖嗎?其實不是的,整個Singleton類中,對mSingleton進行訪問的地方分爲兩類:讀和寫,而且僅當mSingleton爲null的時候纔會寫,mSingleton一旦創建完畢,後面就只剩下讀操作了,再怎麼高併發也沒什麼關係了,反正mSingleton已經是現成的,直接讀就可以了,看如下采用double-check機制的改進代碼:
public class Singleton {  
  1.   
  2.     private volatile static Singleton sSingleton;  
  3.   
  4.     private Singleton() {  
  5.     }  
  6.   
  7.     public static Singleton getInstance() {  
  8.         if (sSingleton == null) { // line A   
  9.             synchronized (Singleton.class) { // line C   
  10.                 if (sSingleton == null)  
  11.                 sSingleton = new Singleton();  // line B   
  12.             }  
  13.         }  
  14.   
  15.         return sSingleton;  
  16.     }  
  17.   
  18. }  
public class Singleton {

    private volatile static Singleton sSingleton;

    private Singleton() {
    }

    public static Singleton getInstance() {
        if (sSingleton == null) { // line A
            synchronized (Singleton.class) { // line C
                if (sSingleton == null)
                sSingleton = new Singleton();  // line B
            }
        }

        return sSingleton;
    }

}
上述代碼近乎完美,可以滿足幾乎所有場合(採用反射和類加載器另當別論)。上述代碼的好處在於:第一次創建實例的時候會同步所有線程,以後有線程再想獲取Singleton的實例就不需要進行同步,直接返回實例即可。還有double-check的意義在於:假設現在有2個線程A和B同時進入了getInstance方法,線程A執行到line A行,線程B執行到line B行,由於B線程還沒有初始化完畢,sSingleton還是null,於是線程A通過了sSingleton==null的判斷,並且往下執行,碰巧,當線程A執行到line C的時候,線程B初始化完畢了,然後線程B返回,注意,如果沒有double-check,這個時候線程A就執行到了line B,就會再次初始化sSingleton,這個時候Singleton實際上被new了兩次,已經不算完全意義上的單例了,而有了double-check,就會再進行一次爲null的判斷,由於B線程已經初始化了sSingleton,所以A線程就不會再次初始化sSingleton。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章