单例模式的多种实现
單例模式是一種常用的軟件設(shè)計模式。在它的核心結(jié)構(gòu)中只包含一個被稱為單例的特殊類。通過單例模式可以保證系統(tǒng)中一個類只有一個實例。當(dāng)一個類的實例有且只可以有一個的時候就需要用到單例模式了。為什么只需要有一個呢?有人說是為了節(jié)約內(nèi)存,但這只是單例模式帶來的一個好處。只有一個實例確實減少內(nèi)存占用,可是我認(rèn)為這不是使用單例模式的理由。我認(rèn)為使用單例模式的時機(jī)是當(dāng)實例存在多個會引起程序邏輯錯誤的時候。比如類似有序的號碼生成器這樣的東西,怎么可以允許一個應(yīng)用上存在多個呢?
Singleton模式主要作用是保證在Java應(yīng)用程序中,一個類Class只有一個實例存在。 一般Singleton模式通常有以下五種形式: 第一種形式:懶漢式,線程不安全。 public class Singleton { //私有化屬性private static Singleton instance; //私有化構(gòu)造器private Singleton (){} //提供獲取單例的方法 public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }這種寫法是明顯的lazy loading,當(dāng)不需要用到單例對象的時候,不會實例化對象。但是致命的缺點(diǎn)是在多線程環(huán)境下不能正常工作。
第二種形式:懶漢式,線程安全。
public class Singleton { //私有化屬性private static Singleton instance; //私有化構(gòu)造器private Singleton (){} //提供了一個供外部訪問類的對象的靜態(tài)方法,可以直接訪問public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }這種寫法既能夠在多線程環(huán)境中很好的工作,而且看起來它也具備很好的lazy loading,但是,遺憾的是,效率很低,99%情況下不需要同步。
第三種形式:餓漢式。
public class Singleton{//在類自己內(nèi)部定義自己的一個實例,只供內(nèi)部調(diào)用private static final Singleton instance = new Singleton();//私有化構(gòu)造器private Singleton(){}//這里提供了一個供外部訪問本類實例的靜態(tài)方法,可以直接訪問public static Singleton getInstance(){return instance;} }這種方式基于classloder機(jī)制避免了多線程的同步問題,不過,instance在類裝載時就會被實例化,雖然導(dǎo)致類裝載的原因有很多種,在單例模式中大多數(shù)都是調(diào)用getInstance方法,?但是也不能確定有其他的方式(或者其他的靜態(tài)方法)導(dǎo)致類裝載,這時候初始化instance顯然沒有達(dá)到lazy loading的效果,所以餓漢式的缺點(diǎn)就是沒法實現(xiàn)懶加載。
第四種形式:靜態(tài)內(nèi)部類。
public class Singleton { //靜態(tài)內(nèi)部類private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } //私有化構(gòu)造器private Singleton (){}//提供一個供外部訪問本類實例的靜態(tài)方法public static final Singleton getInstance() { return SingletonHolder.INSTANCE; } }這種方式同樣利用了classloder的機(jī)制來保證初始化instance時只有一個線程,它跟第三種方式不同的是(很細(xì)微的差別):第三種方式是只要Singleton類被裝載了,那么instance就會被實例化(沒有達(dá)到lazy loading效果),而這種方式是Singleton類被裝載了,instance不一定被初始化。因為SingletonHolder類沒有被主動使用,只有顯示通過調(diào)用getInstance方法時,才會顯示裝載SingletonHolder類,從而實例化instance。想象一下,如果實例化instance很消耗資源,我想讓他延遲加載,另外一方面,我不希望在Singleton類加載時就實例化,因為我不能確保Singleton類還可能在其他的地方被主動使用從而被加載,那么這個時候?qū)嵗痠nstance顯然是不合適的。這個時候,這種方式相比第三種方式就顯得很合理。
第五種形式:雙重校驗鎖的形式。
public class Singleton { // private volatile static Singleton singleton; //私有化構(gòu)造器private Singleton (){}//提供一個供外部訪問本類實例的靜態(tài)方法 public static Singleton getSingleton() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } }這個是第二種方式的升級版,俗稱雙重檢查鎖定,詳細(xì)介紹請查看:http://www.ibm.com/developerworks/cn/java/j-dcl.html
在JDK1.5之后,雙重檢查鎖定才能夠正常達(dá)到單例效果。
總結(jié)
有兩個問題需要注意:
1.如果單例由不同的類裝載器裝入,那便有可能存在多個單例類的實例。假定不是遠(yuǎn)端存取,例如一些servlet容器對每個servlet使用完全不同的類裝載器,這樣的話如果有兩個servlet訪問一個單例類,它們就都會有各自的實例。
2.如果Singleton實現(xiàn)了java.io.Serializable接口,那么這個類的實例就可能被序列化和復(fù)原。不管怎樣,如果你序列化一個單例類的對象,接下來復(fù)原多個那個對象,那你就會有多個單例類的實例。
對第一個問題修復(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層實現(xiàn)了線程安全(如果不是多個類加載器環(huán)境),一般的情況下,我會使用第三種方式,只有在要明確實現(xiàn)lazy loading效果時才會使用第四種方式,另外,單例模式最重要的是一直要保證程序是線程安全的,所以我永遠(yuǎn)不會使用第一種方式,如果有其他特殊的需求,我可能會使用第五種方式,畢竟,JDK1.5已經(jīng)沒有雙重檢查鎖定的問題了。
轉(zhuǎn)載于:https://www.cnblogs.com/Kevin-mao/p/5969227.html
與50位技術(shù)專家面對面20年技術(shù)見證,附贈技術(shù)全景圖總結(jié)
- 上一篇: 腾讯一面有感(移动开发岗位)
- 下一篇: Duilib实现圆形头像控件