淺議單例模式之線程安全

[摘要] 單例模式是一種常見的設計模式,在Java應用中,單例對象能保證在一個JVM中,該對象只有一個實例存在。正是因爲這個特色,單例對象一般做爲程序中的存放配置信息的載體,由於它能保證其餘對象讀到一致的信息。這種方式只需訪問該單例對象便可達到統一可是在多線程環境下,可是隨着應用場景的不一樣,也可能帶來一些同步問題。  面試

    本文將探討一下在多線程環境下,使用單例對象時可能會帶來的同步問題,並給出可選的解決辦法。設計模式

[關鍵字] Java 設計模式  單例  線程 同步  雙重檢查鎖安全

[概念]多線程

單例模式分類:懶漢式單例、餓漢式單例兩種。
單例模式特色:
  1、單例類只能有一個實例
  2、單例類必須本身本身建立本身的惟一實例
  3、單例類必須給全部其餘對象提供這一實例
併發

單例模式確保某個類只有一個實例,並且自行實例化並向整個系統提供這個實例性能

[問題描述]測試

     面試的時候,你們也許會被問到這樣一個問題:請您寫出一個單例模式(Singleton Pattern ,固然都感受比較簡單,代碼以下:spa

/**.net

 * 演示單例模式之飢餓模式線程

 * @author Administrator

 *

 */

public class EagerSingleton

 {

   private static EagerSingleton  instance=new EagerSingleton();

     

    private EagerSingleton()

    {      

    }

    public   static  EagerSingleton  getSingleInstance()

    {      

        return instance;

    }

}

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

/**

 * 演示單例模式之懶漢模式

 * @author Administrator

 *

 */

public class LazySingleton {

 

    private static LazySingleton  instance;   

    private LazySingleton()

    {

       

    }

   

    public   static  LazySingleton  getSingleInstance()

    {

        if (instance == null)

        {

                    instance = new LazySingleton();

        }     

        return instance;

    }  

}

     這種寫法就是所謂的懶漢模式。它使用了延遲加載來保證對象在沒有使用以前,是不會進行初始化的。可是,一般這個時候面試官又會提問新的問題來刁難一下。他會問:這種寫法線程安全嗎?回答必然是:不安全。

測試結果:

    這是由於在多個線程可能同時運行到判斷instance null,因而同時進行了初始化。因此,這是面臨的問題是如何使得這個代碼線程安全?很簡單,在那個方法前面加一個SynchronizedOK

/**

 * 演示單例模式之線程安全

 * @author Administrator

 *

 */

public class ThreadSafeSingleton

 {

    private static ThreadSafeSingleton  instance;    

   

  private ThreadSafeSingleton()

    {      

    }  

  public static synchronized ThreadSafeSingleton  getSingleInstance()

    {

        if (instance == null)

        {

                    instance = new ThreadSafeSingleton();

         }    

        return instance;

    }  

}

 

     寫到這裏,面試官可能仍然會狡猾的看了你一眼,繼續刁難到:這個寫法有沒有什麼性能問題呢?答案確定是有的!同步的代價必然會必定程度的使程序的併發度下降。那麼有沒有什麼方法,一方面是線程安全的,有能夠有很高的併發度呢?咱們觀察到,線程不安全的緣由實際上是在初始化對象的時候,因此,能夠想辦法把同步的粒度下降,只在初始化對象的時候進行同步。

[解決方案]

這裏有必要提出一種新的設計思想——雙重檢查鎖(Double-Checked Lock)。

/**

 * 演示單例模式之雙重鎖定

 * @author Administrator

 *

 */

public class DoubleCheckedSingleton {

 

    private static DoubleCheckedSingleton  instance;    

    private DoubleCheckedSingleton()

    {

 

    }

public static synchronized DoubleCheckedSingleton getSingleInstance()

    {

        //性能改進——雙重鎖定: Double-Check Locking

        if(instance==null)   //  1. 先判斷

        {

            synchronized (DoubleCheckedSingleton.class) // 2. 再同步

            {

                if (instance == null)  //3. 再判斷

                {

                    instance = new DoubleCheckedSingleton(); //4. 實例化

                }

            }

        }  

        return instance;

    }  

}

     這種寫法使得只有在加載新的對象進行同步,在加載完了以後,其餘線程就能夠判斷當前實例對象是否爲空,如非空,並跳過鎖的的代價直接返回當前單例對象了。作到很好的併發度。

     至此,上面的寫法一方面實現了Lazy-Load,另外一個方面也作到了併發度很好的線程安全,一切看上很完美。

     這是,面試官可能會對你的回答滿意的點點頭

     可是,當你此時提出說,其實這種寫法仍是有問題的!面試官也許會對你另眼相看!!

     問題在哪裏?假設線程A執行到調用上述getSingleInstance()方法,它判斷對象爲空,因而線程A執行下面初始化這個對象,但初始化是須要耗費時間的,可是這個對象的地址其實已經存在了。此時若是線程B也執行調用上述getSingleInstance()方法,它判斷不爲空,因而直接跳到最後,返回獲得了這個對象。可是,這個對象還沒有被完整的初始化!獲得一個沒有完全初始化徹底的對象有什麼用!!

關於這個Double-Checked Lock的討論有不少,目前公認這是一個Anti-Pattern(即:反面模式),不推薦使用!因此當這個面試官聽到你的這番答覆,他會不會被Hold不住呢?

 

那麼有沒有什麼更好的寫法呢?

有!這裏又要提出一種新的模式——Initialization on Demand  Holder. 這種方法使用內部類來作到延遲加載對象,在初始化這個內部類的時候,JLS(Java Language Sepcification)會保證這個類的線程安全。這種寫法最大的巧妙在於,徹底使用了Java虛擬機的機制進行同步保證,沒有一個同步的關鍵字。

/**

 * 演示單例模式之完美實現

 * @author Administrator

 *

 */

public class Singleton   

{   

   private static class SingletonHolder   

   {   

        public final static Singleton instance = new Singleton();   

   }   

  

   public static Singleton getInstance()   

   {   

        return SingletonHolder.instance;   

   }   

} 

測試結果:

單個線程

 

多線程

 

至此,單例模式以及線程安全,咱們作了一個系統的比較,但願對你有所幫助!

相關文章
相關標籤/搜索