閱讀943 返回首頁    go 技術社區[雲棲]


Java中線程安全的單例模式

先看一個單例模式

public final class EagerSingleton 

    private static EagerSingleton singObj = new EagerSingleton(); 
    private EagerSingleton(){ 
    } 
 
    public static EagerSingleton getSingleInstance(){ 
       return singObj;
    } 

這就是所謂的餓漢模式,每個對象在沒有使用之前就已經初始化了。這就可能帶來潛在的性能問題:若這個對象很大呢?沒有使用這個對象之前,就把它加載到了內存中去是一種巨大的浪費。針對這種情況可對以上的代碼進行改進,使用一種新的設計思想——延遲加載。

public final class LazySingleton 

    private static LazySingleton singObj = null; 
 
    private LazySingleton(){ 
    } 
 
    public static LazySingleton getSingleInstance(){ 
        if(null == singObj) singObj = new LazySingleton();
          return singObj;
    } 

這就是所謂的懶漢模式。它使用了延遲加載來保證對象在沒有使用之前,是不會進行初始化的。這種寫法線程不安全。這是因為在多個線程可能同時運行到if(null == singObj),判斷singObj為null,於是同時進行了初始化。所以麵臨問題是如何使得代碼線程安全?加上synchronized即可。

public final class ThreadSafeSingleton 

    private static ThreadSafeSingleton singObj = null; 
 
    private ThreadSafeSingleton(){ 
    } 
 
    public static Synchronized ThreadSafeSingleton getSingleInstance(){ 
        if(null == singObj ) singObj = new ThreadSafeSingleton();
            return singObj;
    } 

這個寫法會帶來性能問題。同步的代價必然會一定程度的使程序的並發度降低。有沒有方法一方麵是線程安全的,另一方麵有很高的並發度呢?

我們觀察到線程不安全的原因其實是在初始化對象時,所以可想辦法把同步的粒度降低,隻在初始化對象的時候進行同步。這裏有必要提出一種新的設計思想——雙重檢查鎖(Double-Checked Lock)。

public final class DoubleCheckedSingleton 

    private static DoubleCheckedSingletonsingObj = null; 
 
    private DoubleCheckedSingleton(){ 
    } 
 
    public static DoubleCheckedSingleton getSingleInstance(){ 
       if(null == singObj ) {
             Synchronized(DoubleCheckedSingleton.class){
                    if(null == singObj)
                          singObj = new DoubleCheckedSingleton();
             }
       }
       return singObj;
    } 

這種寫法使得隻有在加載新對象進行同步,在加載完了之後其他線程在第九行就可以判斷跳過鎖的的代價直接到第15行代碼了。做到很好的並發度。


上麵的寫法一方麵實現了Lazy-Load,另一個方麵也做到了並發度很好的線程安全,一切看上很完美。其實這種寫法還是有問題的!問題在哪裏?假設線程A執行到了第9行,它判斷對象為空,於是線程A執行到第12行去初始化這個對象,但初始化是需要耗費時間的,但是這個對象的地址其實已經存在了。此時線程B也執行到了第九行,它判斷不為空,於是直接跳到15行得到了這個對象。但是,這個對象還沒有被完整的初始化!得到一個沒有初始化完全的對象有什麼用!關於這個Double-Checked Lock的討論有很多,目前公認這是一個Anti-Pattern,不推薦使用!

 

這裏又要提出一種新的模式——Initialization on Demand Holder. 這種方法使用內部類來做到延遲加載對象,在初始化這個內部類的時候,JLS(Java Language Sepcification)會保證這個類的線程安全。這種寫法完全使用了Java虛擬機的機製進行同步保證,沒有一個同步的關鍵字。這種方式是Singleton類被裝載了,instance不一定被初始化,因為SingletonHolder類沒有被主動使用。隻有顯示通過調用getInstance方法時,才會顯示裝載SingletonHolder類,從而實例化instance。


public class Singleton   
{   
    private static class SingletonHolder   
    {   
        public final static Singleton instance = new Singleton();   
    }   
  
    public static Singleton getInstance()   
    {   
        return SingletonHolder.instance;   
    }   
}


原帖地址:

https://blog.sina.com.cn/s/blog_75247c770100yxpb.html

https://www.infoq.com/cn/articles/double-checked-locking-with-delay-initialization

https://www.blogjava.net/kenzhh/archive/2013/03/15/357824.html


最後更新:2017-04-03 16:48:57

  上一篇:go Java中線程安全的單例模式
  下一篇:go RTP 協議