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;
}
}
這個是第二種方式的升級版,俗稱雙重檢查鎖定,詳細介紹請查看: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已經沒有雙重檢查鎖定的問題了。
========================================================================
superheizai同學總結的很到位:
不過一般來說,第一種不算單例,第四種和第三種就是一種,如果算的話,第五種也可以分開寫了。所以說,一般單例都是五種寫法。懶漢,惡漢,雙重校驗鎖,枚舉和靜態內部類。
我很高興有這樣的讀者,一起共勉。
頂9
踩分享到: 年終總結上的經驗體會(脫水版) | 重構實踐之一
評論
50 樓 h416373073 2015-01-13 huntfor 寫道lee372106501 寫道第四種明顯有問題嘛,靜態代碼塊裏可以訪問非靜態變量?靜態方法裏可以寫this.?這不是忽悠嗎,你編譯能通過的啊
頂
49 樓 huntfor 2014-09-10 lee372106501 寫道第四種明顯有問題嘛,靜態代碼塊裏可以訪問非靜態變量?靜態方法裏可以寫this.?這不是忽悠嗎,你編譯能通過的啊
頂48 樓 lee372106501 2014-09-08 第四種明顯有問題嘛,靜態代碼塊裏可以訪問非靜態變量?靜態方法裏可以寫this.?這不是忽悠嗎,你編譯能通過的啊47 樓 fs_plane 2014-07-01 有人說雙重校驗沒意義
的確在jdk1.5之前是沒意義
但在jdk的里程碑版本1.5之後就變得很有意義 而且現在以及被廣泛使用 因爲引入了修飾符volatile
這也是爲什麼說jdk1.5才能使用雙重校驗46 樓 H4X0R 2014-05-13 請教一下,new Singleton() 如果需要傳參數才能實例化怎麼辦?第三種方式45 樓 newLinuxJava 2013-12-06 不知博主能否提供下多線程的測試代碼,主要想看看上面例子對問題的重現。44 樓 jis117 2013-10-19 cantellow 寫道senton 寫道第四種(餓漢,變種):
如何獲取Singleton的實例?
呵呵,你太仔細了,是我的錯。我改正。
還沒有改過來
43 樓 cantellow 2011-09-23 nocb 寫道我寫的一個單例, 在tomcat6上, 一個servlet put 數據,一個get數據。
在pc 瀏覽器、手機的瀏覽器上都沒有問題, 但是同事寫的一個手機應用,發送請求,就發現單例變成了2個 對象。非常奇怪。不知道有沒有這方面的經歷??謝謝
估計是類加載器的原因吧,像這種應用,讓web服務器確保單例根本就不合適,多個web服務器更是如此,你可以採用其他方式,比如放在memcache42 樓 nocb 2011-09-22 我寫的一個單例, 在tomcat6上, 一個servlet put 數據,一個get數據。
在pc 瀏覽器、手機的瀏覽器上都沒有問題, 但是同事寫的一個手機應用,發送請求,就發現單例變成了2個 對象。非常奇怪。不知道有沒有這方面的經歷??謝謝41 樓 nocb 2011-09-22 你好,請問 類是不同的加載器裝載的問題怎麼解決?
你寫的沒有看懂, 我的單例怎麼寫,怎麼調用呢?40 樓 cymx09 2010-12-24 你的這7種方法不錯。不過,最好,每個都加上私有構造方法:防止外部直接 new Singleton() 這樣才能真正保證單例。。
private Singleton(){
}39 樓 xiaohui886688 2010-12-21 受益匪淺,。。。
38 樓 清晨陽光 2010-12-17 如果用第三種,建議聲明爲final的,因爲我們沒有理由不將其聲明爲final的。另外,關於雙重鎖定,建議你看看EhCache的源代碼裏的CacheManager類,new了新的實力後,應該在synchronized塊內return,其餘一致,包括將實例聲明爲原子的。
附EhCache項目CacheManager部分源代碼:
Java代碼
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塊內返回的,而你的例子不是
}
}
37 樓 yunchow 2010-12-17 搞這麼多沒意思 , 直接餓漢纔是王道36 樓 kyfxbl 2010-12-17 建議你深入學習一下“茴”的4種寫法35 樓 guyinglong 2010-12-16 public class Singleton {
/**
* 類級的內部類,也就是靜態的成員式內部類,該內部類的實例與外部類的實例
* 沒有綁定關係,而且只有被調用到纔會裝載,從而實現了延遲加載
*/
private static class SingletonHolder{
/**
* 靜態初始化器,由JVM來保證線程安全
*/
private static Singleton instance = new Singleton();
}
/**
* 私有化構造方法
*/
private Singleton(){
}
public static Singleton getInstance(){
return SingletonHolder.instance;
}
}