1. 程式人生 > >Java設計模式之單例多例模式

Java設計模式之單例多例模式

一  初識

java的單例模式主要有四種實現方式:餓漢式、懶漢式、雙重檢查鎖式、靜態內部類。

廢話不多說,直接上程式碼

  • 餓漢式

缺點:浪費空間。不管用不用都在類裝載時初始化物件。

public class SingletonHungry {

    private static final SingletonHungry instance = new SingletonHungry();
   /**
     * 遮蔽外面直接new例項化物件
     */
    private SingletonHungry() {
    }

    /**
     * 餓漢式:
     *       類載入時初始化一次(且只初始化一次)物件(static修飾),且instance不能被覆蓋(final修飾)
     * @return
     */
    public static SingletonHungry getInstance(){
        return instance;
    }

}
  • 懶漢式

缺點:加了synchonized關鍵字,效能上有損耗,在99%的情況下都不存在安全問題。

public class SingletonLazy {
    private static SingletonLazy instance = null;
   /**
     * 遮蔽外面直接new例項化物件
     */
    private SingletonLazy(){
    };

    /**
     * 懶漢式:
     * static :通過類直接訪問。static修飾的成員變數(不能修飾非成員變數)以及靜態程式碼塊在類初始化就載入,
     *          static修飾的方法需要在呼叫時才載入。這樣就能保證物件例項的懶載入。
     * synchronized :為了保證線性安全問題必須加鎖
     * @return
     */
    public static synchronized SingletonLazy getInstance(){
        if(instance==null){
            instance =  new SingletonLazy();
        }
        return  instance;
    }
}

雙重檢查鎖式:

public class SingletonDoubleCheck {
    private static SingletonDoubleCheck instance = null;
   /**
     * 遮蔽外面直接new例項化物件
     */
    private SingletonDoubleCheck(){
    };

    /**
     * 雙重檢查鎖:
     * 兩次判斷instance 是否為null,雙重檢查,且在最後一次判斷之前給後續程式碼塊加鎖synchonized
     * @return
     */
    public static SingletonDoubleCheck getInstance(){
        if (instance==null){
            synchronized(SingletonDoubleCheck.class){
                if(instance==null){
                    instance = new SingletonDoubleCheck();
                }
            }
        }
        return 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;
    }
}

總結  有兩個問題需要注意:  1、如果單例由不同的類裝載器裝入,那便有可能存在多個單例類的例項。假定不是遠端存取,例如一些servlet容器對每個servlet使用完全不同的類 裝載器,這樣的話如果有兩個servlet訪問一個單例類,它們就都會有各自的例項。  2、如果Singleton實現了java.io.Serializable介面,那麼這個類的例項就可能被序列化和復原。不管怎樣,如果你序列化一個單例類的物件,接下來複原多個那個物件,那你就會有多個單例類的例項。

對第一個問題(類裝載器可以生成多個例項)修復的辦法是:

PS:有點像實現類裝載器的單例

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

對第二個問題(多次反序列化會復原多個例項物件)修復的辦法是:

PS:JVM從記憶體中反序列化地"組裝"一個新物件時,就會自動呼叫這個 readResolve方法來返回我們指定好的物件了, 單例規則也就得到了保證.

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

單例模式的關鍵有兩點:  1、構造方法為私有,這樣外界就不能隨意呼叫。  2、get例項的方法為靜態,由類直接呼叫

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

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

單例和多例的詳細描述:  1. 什麼是單例多例:  所謂單例就是所有的請求都用一個物件來處理,比如我們常用的service和dao層的物件通常都是單例的,而多例則指每個請求用一個新的物件來處理,比如action;  2. 如何產生單例多例:  在通用的SSH中,單例在spring中是預設的,如果要產生多例,則在配置檔案的bean中新增scope=”prototype”;  3. 為什麼用單例多例:  之所以用單例,是因為沒必要每個請求都新建一個物件,這樣子既浪費CPU又浪費記憶體;  之所以用多例,是為了防止併發問題;即一個請求改變了物件的狀態,此時物件又處理另一個請求,而之前請求對物件狀態的改變導致了物件對另一個請求做了錯誤的處理;  用單例和多例的標準只有一個:  當物件含有可改變的狀態時(更精確的說就是在實際應用中該狀態會改變),則多例,否則單例;  4. 何時用單例?何時用多例?  對於struts2來說,action必須用多例,因為action本身含有請求引數的值,即可改變的狀態;  而對於STRUTS1來說,action則可用單例,因為請求引數的值是放在actionForm中,而非action中的;  另外要說一下,並不是說service或dao一定是單例,標準同第3點所講的,就曾見過有的service中也包含了可改變的狀態,同時執行方法也依賴該狀態,但一樣用的單例,這樣就會出現隱藏的BUG,而併發的BUG通常很難重現和查詢;