原文作者:Cantellow
原文地址:原文鏈接
摘抄申明:我們不占有不侵權(quán)迹辐,我們只是好文的搬運(yùn)工蝶防!轉(zhuǎn)發(fā)請帶上原文申明。
第一種(懶漢明吩,線程不安全):
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
第二種(懶漢间学,線程安全):
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機(jī)制
,在深度分析Java的ClassLoader機(jī)制(源碼級別)和Java類的加載水泉、鏈接和初始化兩個文章中有關(guān)于Classload而機(jī)制的線程安全問題的介紹善涨,避免了多線程的同步問題,不過草则,instance在類裝載時就實(shí)例化躯概,雖然導(dǎo)致類裝載的原因有很多種,在單例模式中大多數(shù)都是調(diào)用getInstance方法畔师, 但是也不能確定有其他的方式(或者其他的靜態(tài)方法)導(dǎo)致類裝載娶靡,這時候初始化instance顯然沒有達(dá)到lazy loading的效果。
第四種(餓漢看锉,變種):
public class Singleton {
private Singleton instance = null;
static {
instance = new Singleton();
}
private Singleton (){}
public static Singleton getInstance() {
return this.instance;
}
}
表面上看起來差別挺大姿锭,其實(shí)更第三種方式差不多,都是在類初始化即實(shí)例化instance伯铣。
第五種(靜態(tài)內(nèi)部類):
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的機(jī)制來保證初始化instance時只有一個線程呻此,它跟第三種和第四種方式不同的是(很細(xì)微的差別):第三種和第四種方式是只要Singleton類被裝載了,那么instance就會被實(shí)例化(沒有達(dá)到lazy loading效果)腔寡,而這種方式是Singleton類被裝載了焚鲜,instance不一定被初始化。因?yàn)镾ingletonHolder類沒有被主動使用放前,只有顯示通過調(diào)用getInstance方法時忿磅,才會顯示裝載SingletonHolder類,從而實(shí)例化instance凭语。想象一下葱她,如果實(shí)例化instance很消耗資源,我想讓他延遲加載似扔,另外一方面吨些,我不希望在Singleton類加載時就實(shí)例化搓谆,因?yàn)槲也荒艽_保Singleton類還可能在其他的地方被主動使用從而被加載,那么這個時候?qū)嵗痠nstance顯然是不合適的豪墅。這個時候泉手,這種方式相比第三和第四種方式就顯得很合理。
第六種(枚舉):
public enum SingletonEnum{
INSTANCE;
private Singleton singleton ;
// JVM保證這個方法絕對只調(diào)用一次
SingletonEnum(){
singleton = new Singleton();
}
public Singleton getInstance() {
return singleton ;
}
}
調(diào)用:Singleton singleton = SingletonEnum.INSTANCE.getInstance();
這種方式是Effective Java作者Josh Bloch 提倡的方式偶器,它不僅能避免多線程同步問題螃诅,而且還能防止反序列化重新創(chuàng)建新的對象,可謂是很堅(jiān)強(qiáng)的壁壘啊状囱,在深度分析Java的枚舉類型——枚舉的線程安全性及序列化問題中有詳細(xì)介紹枚舉的線程安全問題和序列化問題(枚舉類沒有構(gòu)造方法术裸,不會被反序列化),不過亭枷,個人認(rèn)為由于1.5中才加入enum特性袭艺,用這種方式寫不免讓人感覺生疏,在實(shí)際工作中叨粘,我也很少看見有人這么寫過猾编。
原文補(bǔ)充:這個枚舉沒看出來在哪創(chuàng)造的對象,所以有上網(wǎng)查了一下 java單例之enum實(shí)現(xiàn)方式
public class EnumSingleton{
private EnumSingleton(){}
public static EnumSingleton getInstance(){
return Singleton.INSTANCE.getInstance();
}
private static enum Singleton{
INSTANCE;
private EnumSingleton singleton;
//JVM會保證此方法絕對只調(diào)用一次
private Singleton(){
singleton = new EnumSingleton();
}
public EnumSingleton getInstance(){
return singleton;
}
}
}
第七種(雙重校驗(yàn)鎖):
public class Singleton {
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) { // 出于性能考慮升敲,第一次判斷null答倡,避免已實(shí)例化再次進(jìn)入同步代碼塊
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
這個是第二種方式的升級版,俗稱雙重檢查鎖定驴党,詳細(xì)介紹請查看:http://www.ibm.com/developerworks/cn/java/j-dcl.html已失效瘪撇,重新?lián)Q了鏈接
在JDK1.5之后,雙重檢查鎖定才能夠正常達(dá)到單例效果港庄。
總結(jié)
有兩個問題需要注意:
1.如果單例由不同的類裝載器裝入倔既,那便有可能存在多個單例類的實(shí)例。假定不是遠(yuǎn)端存取鹏氧,例如一些servlet容器對每個servlet使用完全不同的類裝載器渤涌,這樣的話如果有兩個servlet訪問一個單例類,它們就都會有各自的實(shí)例把还。
2.如果Singleton實(shí)現(xiàn)了java.io.Serializable接口实蓬,那么這個類的實(shí)例就可能被序列化和復(fù)原。不管怎樣吊履,如果你序列化一個單例類的對象安皱,接下來復(fù)原多個那個對象,那你就會有多個單例類的實(shí)例率翅。
對第一個問題修復(fù)的辦法是:
private static Class getClass(String classname)
throws ClassNotFoundException {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
if(classLoader == null)
classLoader = Singleton.class.getClassLoader();
return (classLoader.loadClass(classname));
}
}
對第二個問題修復(fù)的辦法是:
public class Singleton implements java.io.Serializable {
public static Singleton INSTANCE = new Singleton();
protected Singleton() {
}
private Object readResolve() {
return INSTANCE;
}
}
對我來說练俐,我比較喜歡第三種和第五種方式袖迎,簡單易懂冕臭,而且在JVM層實(shí)現(xiàn)了線程安全(如果不是多個類加載器環(huán)境)腺晾,一般的情況下,我會使用第三種方式辜贵,只有在要明確實(shí)現(xiàn)lazy loading效果時才會使用第五種方式悯蝉,另外,如果涉及到反序列化創(chuàng)建對象時我會試著使用枚舉的方式來實(shí)現(xiàn)單例托慨,不過鼻由,我一直會保證我的程序是線程安全的,而且我永遠(yuǎn)不會使用第一種和第二種方式厚棵,如果有其他特殊的需求蕉世,我可能會使用第七種方式,畢竟婆硬,JDK1.5已經(jīng)沒有雙重檢查鎖定的問題了狠轻。
superheizai同學(xué)總結(jié)的很到位:
不過一般來說,第一種不算單例彬犯,第四種和第三種就是一種向楼,如果算的話,第五種也可以分開寫了谐区。所以說湖蜕,一般單例都是五種寫法。懶漢宋列,惡漢昭抒,雙重校驗(yàn)鎖,枚舉和靜態(tài)內(nèi)部類炼杖。