1. 程式人生 > >單例模式和多例的區別

單例模式和多例的區別

設計者模式-單例模式和多例的區別

單例模式的關鍵有兩點:

1、構造方法為私有,這樣外界就不能隨意呼叫。
2、get的方法為靜態,由類直接呼叫

多例模式(Multiton)

1 、多例類可以有多個例項
2 、多例類必須能夠自我建立並管理自己的例項,並向外界提供自己的例項。

一、單例模式和多例模式說明:

  1. 單例模式和多例模式屬於物件模式。
  2. 單例模式的物件在整個系統中只有一份,多例模式可以有多個例項。
  3. 它們都不對外提供構造方法,即構造方法都為私有。

單例和多例的詳細描述:

  1. 什麼是單例多例:
    所謂單例就是所有的請求都用一個物件來處理,比如我們常用的service和dao層的物件通常都是單例的,而多例則指每個請求用一個新的物件來處理,比如action;
  2. 如何產生單例多例:
    在通用的SSH中,單例在spring中是預設的,如果要產生多例,則在配置檔案的bean中新增scope=”prototype”;
  3. 為什麼用單例多例:
    1).之所以用單例,是因為沒必要每個請求都新建一個物件,這樣子既浪費CPU又浪費記憶體;
    2).之所以用多例,是為了防止併發問題;即一個請求改變了物件的狀態,此時物件又處理另一個請求,而之前請求對物件狀態的改變導致了物件對另一個請求做了錯誤的處理;
用單例和多例的標準只有一個:

當物件含有可改變的狀態時(更精確的說就是在實際應用中該狀態會改變),則多例,否則單例;
4. 何時用單例?何時用多例?
對於struts2來說,action必須用多例,因為action本身含有請求引數的值,即可改變的狀態;
而對於STRUTS1來說,action則可用單例,因為請求引數的值是放在actionForm中,而非action中的;
另外要說一下,並不是說service或dao一定是單例,標準同第3點所講的,就曾見過有的service中也包含了可改變的狀態,同時執行方法也依賴該狀態,但一樣用的單例,這樣就會出現隱藏的BUG,而併發的BUG通常很難重現和查詢;

二、單例的寫法

第一種(懶漢,執行緒不安全):
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 Singleton instance = new Singleton();
    private Singleton (){}
    public static Singleton getInstance() {
        return instance;
    }  
} 

這種方式基於classloder機制避免了多執行緒的同步問題,不過,instance在類裝載時就例項化,雖然導致類裝載的原因有很多種,在單例模式中大多數都是呼叫getInstance方法, 但是也不能確定有其他的方式(或者其他的靜態方法)導致類裝載,這時候初始化instance顯然沒有達到lazy loading的效果。

第四種(餓漢,變種):
public class Singleton {
    private Singleton instance = null;
    static {
        instance = new Singleton();
    }
    private Singleton (){}
    public static Singleton getInstance() {
        return this.instance;
    }  
} 

表面上看起來差別挺大,其實更第三種方式差不多,都是在類初始化即例項化instance。

第五種(靜態內部類):
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 enum Singleton {
    INSTANCE;
    public void whateverMethod() {
    }
} 

這種方式是Effective Java作者Josh Bloch 提倡的方式,它不僅能避免多執行緒同步問題,而且還能防止反序列化重新建立新的物件,可謂是很堅強的壁壘啊,不過,個人認為由於1.5中才加入enum特性,用這種方式寫不免讓人感覺生疏,在實際工作中,我也很少看見有人這麼寫過

第七種(雙重校驗鎖):
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;
    }  
} 

這個是第二種方式的升級版,俗稱雙重檢查鎖定

總結

有兩個問題需要注意:
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;
    }  
}