單例模式的多種實現

單例模式是一種經常使用的軟件設計模式。在它的核心結構中只包含一個被稱爲單例的特殊類。經過單例模式能夠保證系統中一個類只有一個實例。當一個類的實例有且只能夠有一個的時候就須要用到單例模式了。爲何只須要有一個呢?有人說是爲了節約內存,但這只是單例模式帶來的一個好處。只有一個實例確實減小內存佔用,但是我認爲這不是使用單例模式的理由。我認爲使用單例模式的時機是當實例存在多個會引發程序邏輯錯誤的時候。好比相似有序的號碼生成器這樣的東西,怎麼能夠容許一個應用上存在多個呢?html

Singleton模式主要做用是保證在Java應用程序中,一個類Class只有一個實例存在。
通常Singleton模式一般有如下五種形式:
第一種形式:懶漢式,線程不安全。
public class Singleton {  
    //私有化屬性
    private static Singleton instance;  
    //私有化構造器
    private Singleton (){}  
    //提供獲取單例的方法 
    public static Singleton getInstance() {  
         if (instance == null) {  
             instance = new Singleton();  
         }  
         return instance;  
    }  
}  

這種寫法是明顯的lazy loading,當不須要用到單例對象的時候,不會實例化對象。可是致命的缺點是在多線程環境下不能正常工做。java

第二種形式:懶漢式,線程安全。設計模式

public class Singleton {  
    //私有化屬性
    private static Singleton instance;  
    //私有化構造器
    private Singleton (){}  
    //提供了一個供外部訪問類的對象的靜態方法,能夠直接訪問
    public static synchronized Singleton getInstance() {  
        if (instance == null) {  
            instance = new Singleton();  
        }  
        return instance;  
    }  
} 

這種寫法既可以在多線程環境中很好的工做,並且看起來它也具有很好的lazy loading,可是,遺憾的是,效率很低,99%狀況下不須要同步。安全

第三種形式:餓漢式。多線程

public class Singleton{
    //在類本身內部定義本身的一個實例,只供內部調用
    private static final Singleton instance = new Singleton();
    //私有化構造器
    private Singleton(){}
    //這裏提供了一個供外部訪問本類實例的靜態方法,能夠直接訪問
    public static Singleton getInstance(){
        return instance;
    }
}

這種方式基於classloder機制避免了多線程的同步問題,不過,instance在類裝載時就會被實例化,雖然致使類裝載的緣由有不少種,在單例模式中大多數都是調用getInstance方法, 可是也不能肯定有其餘的方式(或者其餘的靜態方法)致使類裝載,這時候初始化instance顯然沒有達到lazy loading的效果,因此餓漢式的缺點就是無法實現懶加載。spa

第四種形式:靜態內部類。線程

public class Singleton {  
    //靜態內部類
    private static class SingletonHolder {  
        private static final Singleton INSTANCE = new Singleton(); }  
    //私有化構造器
    private Singleton (){}
    //提供一個供外部訪問本類實例的靜態方法
    public static final Singleton getInstance() {  
        return SingletonHolder.INSTANCE;  
    }  
}  

這種方式一樣利用了classloder的機制來保證初始化instance時只有一個線程,它跟第三種方式不一樣的是(很細微的差異):第三種方式是隻要Singleton類被裝載了,那麼instance就會被實例化(沒有達到lazy loading效果),而這種方式是Singleton類被裝載了,instance不必定被初始化。由於SingletonHolder類沒有被主動使用,只有顯示經過調用getInstance方法時,纔會顯示裝載SingletonHolder類,從而實例化instance。想象一下,若是實例化instance很消耗資源,我想讓他延遲加載,另一方面,我不但願在Singleton類加載時就實例化,由於我不能確保Singleton類還可能在其餘的地方被主動使用從而被加載,那麼這個時候實例化instance顯然是不合適的。這個時候,這種方式相比第三種方式就顯得很合理。設計

第五種形式:雙重校驗鎖的形式。code

public class Singleton {  
    //
    private volatile static Singleton singleton;  
    //私有化構造器
    private Singleton (){}
    //提供一個供外部訪問本類實例的靜態方法  
    public static Singleton getSingleton() {  
        if (singleton == null) {  
            synchronized (Singleton.class) {  
                 if (singleton == null) {  
                    singleton = new Singleton(); } } }  
        return singleton;  
    }  
}              

這個是第二種方式的升級版,俗稱雙重檢查鎖定,詳細介紹請查看:http://www.ibm.com/developerworks/cn/java/j-dcl.htmlhtm

在JDK1.5以後,雙重檢查鎖定纔可以正常達到單例效果。

總結

有兩個問題須要注意:

1.若是單例由不一樣的類裝載器裝入,那便有可能存在多個單例類的實例。假定不是遠端存取,例如一些servlet容器對每一個servlet使用徹底不一樣的類裝載器,這樣的話若是有兩個servlet訪問一個單例類,它們就都會有各自的實例。

2.若是Singleton實現了java.io.Serializable接口,那麼這個類的實例就可能被序列化和復原。無論怎樣,若是你序列化一個單例類的對象,接下來複原多個那個對象,那你就會有多個單例類的實例。

對第一個問題修復的辦法是:

private static Class getClass(String classname)throws ClassNotFoundException {     
      ClassLoader classLoader =Thread.currentThread().getContextClassLoader();     
      
      if(classLoader == null)     
         classLoader = Singleton.class.getClassLoader();     
      
      return (classLoader.loadClass(classname));     
   }     
}  

 對第二個問題修復的辦法是:

public class Singleton implements java.io.Serializable {     
   public static Singleton INSTANCE = new Singleton();     
      
   protected Singleton() {}   
  
   private Object readResolve() {     
            return INSTANCE;     
   }    
}  

第三種和第四種方式,簡單易懂,並且在JVM層實現了線程安全(若是不是多個類加載器環境),通常的狀況下,我會使用第三種方式,只有在要明確實現lazy loading效果時纔會使用第四種方式,另外,單例模式最重要的是一直要保證程序是線程安全的,因此我永遠不會使用第一種方式,若是有其餘特殊的需求,我可能會使用第五種方式,畢竟,JDK1.5已經沒有雙重檢查鎖定的問題了。

相關文章
相關標籤/搜索