1. 程式人生 > >單列模式

單列模式

接口 寫法 加載器 exc subjects close 鎖定 itl 新的

首頁 資訊 精華 論壇 問答 博客 專欄 群組 更多 知識庫搜索 您還未登錄 ! 登錄 註冊

cantellow

  • 博客
  • 微博
  • 相冊
  • 收藏
  • 留言
  • 關於我
cantellow

單例模式的七種寫法

博客分類:
  • 設計模式
多線程JVMServletIBMIDEA

轉載請註明出處:http://cantellow.iteye.com/blog/838473

第一種(懶漢,線程不安全):

Java代碼
  1. public class Singleton {
  2. private static Singleton instance;
  3. private Singleton (){}
  4. public static Singleton getInstance() {
  5. if (instance == null) {
  6. instance = new Singleton();
  7. }
  8. return instance;
  9. }
  10. }
public class Singleton {
    private static Singleton instance;
    private Singleton (){}

    public static Singleton getInstance() {
	if (instance == null) {
	    instance = new Singleton();
	}
	return instance;
    }
}

這種寫法lazy loading很明顯,但是致命的是在多線程不能正常工作。

第二種(懶漢,線程安全):

Java代碼
  1. public class Singleton {
  2. private static Singleton instance;
  3. private Singleton (){}
  4. public static synchronized Singleton getInstance() {
  5. if (instance == null) {
  6. instance = new Singleton();
  7. }
  8. return instance;
  9. }
  10. }
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%情況下不需要同步。

第三種(餓漢):

Java代碼
  1. public class Singleton {
  2. private static Singleton instance = new Singleton();
  3. private Singleton (){}
  4. public static Singleton getInstance() {
  5. return instance;
  6. }
  7. }
public class Singleton {
    private static Singleton instance = new Singleton();
    private Singleton (){}
    public static Singleton getInstance() {
	return instance;
    }
}

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

第四種(漢,變種):

Java代碼
  1. public class Singleton {
  2. private Singleton instance = null;
  3. static {
  4. instance = new Singleton();
  5. }
  6. private Singleton (){}
  7. public static Singleton getInstance() {
  8. return this.instance;
  9. }
  10. }
public class Singleton {
    private Singleton instance = null;
    static {
	instance = new Singleton();
    }
    private Singleton (){}
    public static Singleton getInstance() {
	return this.instance;
    }
}

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

第五種(靜態內部類):

Java代碼
  1. public class Singleton {
  2. private static class SingletonHolder {
  3. private static final Singleton INSTANCE = new Singleton();
  4. }
  5. private Singleton (){}
  6. public static final Singleton getInstance() {
  7. return SingletonHolder.INSTANCE;
  8. }
  9. }
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顯然是不合適的。這個時候,這種方式相比第三和第四種方式就顯得很合理。

第六種(枚舉):

Java代碼
  1. public enum Singleton {
  2. INSTANCE;
  3. public void whateverMethod() {
  4. }
  5. }
public enum Singleton {
    INSTANCE;
    public void whateverMethod() {
    }
}

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

第七種(雙重校驗鎖):

Java代碼
  1. public class Singleton {
  2. private volatile static Singleton singleton;
  3. private Singleton (){}
  4. public static Singleton getSingleton() {
  5. if (singleton == null) {
  6. synchronized (Singleton.class) {
  7. if (singleton == null) {
  8. singleton = new Singleton();
  9. }
  10. }
  11. }
  12. return singleton;
  13. }
  14. }
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接口,那麽這個類的實例就可能被序列化和復原。不管怎樣,如果你序列化一個單例類的對象,接下來復原多個那個對象,那你就會有多個單例類的實例。

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

Java代碼
  1. private static Class getClass(String classname)
  2. throws ClassNotFoundException {
  3. ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
  4. if(classLoader == null)
  5. classLoader = Singleton.class.getClassLoader();
  6. return (classLoader.loadClass(classname));
  7. }
  8. }
private static Class getClass(String classname)    
                                         throws ClassNotFoundException {   
      ClassLoader classLoader = Thread.currentThread().getContextClassLoader();   
    
      if(classLoader == null)   
         classLoader = Singleton.class.getClassLoader();   
    
      return (classLoader.loadClass(classname));   
   }   
}

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

Java代碼
  1. public class Singleton implements java.io.Serializable {
  2. public static Singleton INSTANCE = new Singleton();
  3. protected Singleton() {
  4. }
  5. private Object readResolve() {
  6. return INSTANCE;
  7. }
  8. }
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已經沒有雙重檢查鎖定的問題了。

========================================================================

總結的很到位:

不過一般來說,第一種不算單例,第四種和第三種就是一種,如果算的話,第五種也可以分開寫了。所以說,一般單例都是五種寫法。懶漢,惡漢,雙重校驗鎖,枚舉和靜態內部類。

我很高興有這樣的讀者,一起共勉。

年終總結上的經驗體會(脫水版) | 重構實踐之一
  • 分類:編程語言
  • 相關推薦
參考知識庫
語音識別與合成知識庫 574 關註 | 316 收錄
計算機視覺知識庫 1339 關註 | 252 收錄
自然語言理解和處理知識庫 491 關註 | 87 收錄
知識工程知識庫 157 關註 | 74 收錄
評論
第三種稍微有點Java基礎就知道是錯的,被瞎寫誤導別人,復制粘貼也要動一下腦子 第四種,靜態方法中是不能用this的吧。。 這樣的: 還有一種利用JVM的類加載機制,
它是線程安全的,同時又是在用到的時候才會創建(利用了java虛擬機的類加載特性)
Java代碼
  1. public class ManagerHolder {
  2. public static Manager managerHolder = new Manager();
  3. }
  4. public class Manager {
  5. public static Manager getInstances() {
  6. return ManagerHolder.managerHolder;
  7. }
  8. }
public class ManagerHolder {

    public static Manager managerHolder = new Manager();
    
}

public class Manager {

    public static Manager getInstances() {
        return ManagerHolder.managerHolder;
    }
}
52 樓 Mr.Cheney 2015-08-28 還有一種利用JVM的類加載機制,
它是線程安全的,同時又是在用到的時候才會創建(利用了java虛擬機的類加載特性)
Java代碼
  1. public class ManagerHolder {
  2. public static Manager managerHolder = new Manager();
  3. }
  4. public class ManagerHolder {
  5. public static Manager managerHolder = new Manager();
  6. }
public class ManagerHolder {

    public static Manager managerHolder = new Manager();
    
}

public class ManagerHolder {

    public static Manager managerHolder = new Manager();
    
}

第四種明顯有問題嘛,靜態代碼塊裏可以訪問非靜態變量?靜態方法裏可以寫this.?這不是忽悠嗎,你編譯能通過的 第四種明顯有問題嘛,靜態代碼塊裏可以訪問非靜態變量?靜態方法裏可以寫this.?這不是忽悠嗎,你編譯能通過的啊
第四種明顯有問題嘛,靜態代碼塊裏可以訪問非靜態變量?靜態方法裏可以寫this.?這不是忽悠嗎,你編譯能通過的啊 有人說雙重校驗沒意義
的確在jdk1.5之前是沒意義
但在jdk的裏程碑版本1.5之後就變得很有意義 而且現在以及被廣泛使用 因為引入了修飾符volatile

這也是為什麽說jdk1.5才能使用雙重校驗 new Singleton() 如果需要傳參數才能實例化怎麽辦?第三種方式 第四種(餓漢,變種):
如何獲取Singleton的實例? 我寫的一個單例, 在tomcat6上, 一個servlet put 數據,一個get數據。
在pc 瀏覽器、手機的瀏覽器上都沒有問題, 但是同事寫的一個手機應用,發送請求,就發現單例變成了2個 對象。非常奇怪。不知道有沒有這方面的經歷??謝謝
估計是類加載器的原因吧,像這種應用,讓web服務器確保單例根本就不合適,多個web服務器更是如此,你可以采用其他方式,比如放在memcache 我寫的一個單例, 在tomcat6上, 一個servlet put 數據,一個get數據。
在pc 瀏覽器、手機的瀏覽器上都沒有問題, 但是同事寫的一個手機應用,發送請求,就發現單例變成了2個 對象。非常奇怪。不知道有沒有這方面的經歷?? 類是不同的加載器裝載的問題怎麽解決? 你的這7種方法不錯。不過,最好,每個都加上私有構造方法:防止外部直接 new Singleton() 這樣才能真正保證單例。。
private Singleton(){

} 如果用第三種,建議聲明為final的,因為我們沒有理由不將其聲明為final的。另外,關於雙重鎖定,建議你看看EhCache的源代碼裏的CacheManager類,new了新的實力後,應該在synchronized塊內return,其余一致,包括將實例聲明為原子的。
附EhCache項目CacheManager部分源代碼:
Java代碼
  1. public static CacheManager create() throws CacheException {
  2. if (singleton != null) {
  3. return singleton;
  4. }
  5. synchronized (CacheManager.class) {
  6. if (singleton == null) {
  7. LOG.debug("Creating new CacheManager with default config");
  8. singleton = new CacheManager();
  9. } else {
  10. LOG.debug("Attempting to create an existing singleton. Existing singleton returned.");
  11. }
  12. return singleton; // 這裏是在synchronized塊內返回的,而你的例子不是
  13. }
  14. }
public static CacheManager create() throws CacheException {
    if (singleton != null) {
        return singleton;
    }
    synchronized (CacheManager.class) {
        if (singleton == null) {
            LOG.debug("Creating new CacheManager with default config");
            singleton = new CacheManager();
        } else {
            LOG.debug("Attempting to create an existing singleton. Existing singleton returned.");
        }
        return singleton; // 這裏是在synchronized塊內返回的,而你的例子不是
    }
}
發表評論

您還沒有登錄,請您登錄後再發表評論

cantellow
  • 瀏覽: 525879 次
  • 性別:
  • 來自:
最近訪客 更多訪客>>
文章分類
  • 全部博客 (75)
  • Java (23)
  • C++ (1)
  • 重構 (2)
  • 質量 (9)
  • JVM (3)
  • 項目管理 (6)
  • 數據庫 (1)
  • 設計模式 (4)
  • 並發 (1)
  • web (5)
  • linux (1)
  • maven (1)
  • IOS開發 (4)
  • 小項目 (1)
  • 書籍是人類進步的階梯 (5)
  • 他山之石可以攻玉 (1)
  • 其他 (9)
  • 逆向 (1
最新評論
  • 60love5: 第三種稍微有點Java基礎就知道是錯的,被瞎寫誤導別人,復制粘 ...
    單例模式的七種寫法
  • Bocurry: 第四種,靜態方法中是不能用this的吧。。
    單例模式的七種寫法
  • Mr.Cheney: 這樣的:<div class="quote_t ...
    單例模式的七種寫法
  • Mr.Cheney: 還有一種利用JVM的類加載機制, 它是線程安全的,同時又是在用 ...
    單例模式的七種寫法
  • tianyi1: 第四種有問題
    單例模式的七種寫法

關閉

單列模式