單例模式的七種寫法

喝水會長肉發表於2021-12-12

 寫在前面:單例模式,是設計模式中最簡單的一種,但是,他卻有很多的東西需要注意,效能、執行緒安全等。這篇文章是我轉載的,轉載之後我仔細研究了一下並加了備註和相關知識連結(滑鼠懸浮在帶連結的文字上就可以看到我的註釋,例如: 滑鼠懸浮在這)。

第一種( 懶漢,執行緒不安全):


public 
class 
Singleton 
{  

    private static Singleton instance ;  
    private Singleton ( ) { }  
    public static synchronized 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機制,在 深度分析Java的ClassLoader機制(原始碼級別)Java類的載入、連結和初始化兩個文章中有關於CLassload而機制的執行緒安全問題的介紹,避免了多執行緒的 同步問題,不過, 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 提倡的方式,它不僅能避免多執行緒同步問題,而且還能防止反序列化重新建立新的物件,可謂是很堅強的壁壘啊,在 深度分析Java的列舉型別—-列舉的執行緒安全性及序列化問題中有詳細介紹列舉的執行緒安全問題和序列化問題,不過,個人認為由於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 ;  
    }  
}  

總結

有兩個問題需要注意:

  1. 如果單例由不同的類裝載器裝入,那便有可能存在多個單例類的例項。假定不是遠端存取,例如一些servlet容器對每個servlet使用完全不同的類裝載器,這樣的話如果有兩個servlet訪問一個單例類,它們就都會有各自的例項。

  2. //java學習交流:737251827  進入可領取學習資源及對十年開發經驗大佬提問,免費解答!

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已經沒有雙重檢查鎖定的問題了。

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

(全文完)



來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70010294/viewspace-2847259/,如需轉載,請註明出處,否則將追究法律責任。

相關文章