單例模式的多種實現


單例模式是一種常用的軟件設計模式。在它的核心結構中只包含一個被稱為單例的特殊類。通過單例模式可以保證系統中一個類只有一個實例。當一個類的實例有且只可以有一個的時候就需要用到單例模式了。為什么只需要有一個呢?有人說是為了節約內存,但這只是單例模式帶來的一個好處。只有一個實例確實減少內存占用,可是我認為這不是使用單例模式的理由。我認為使用單例模式的時機是當實例存在多個會引起程序邏輯錯誤的時候。比如類似有序的號碼生成器這樣的東西,怎么可以允許一個應用上存在多個呢?

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,當不需要用到單例對象的時候,不會實例化對象。但是致命的缺點是在多線程環境下不能正常工作。

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

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的效果,所以餓漢式的缺點就是沒法實現懶加載。

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

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顯然是不合適的。這個時候,這種方式相比第三種方式就顯得很合理。

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

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.html

在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已經沒有雙重檢查鎖定的問題了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM