Java 懶漢式單例 餓漢式單例
轉載請註明出處:http://blog.csdn.net/mr_liabill/article/details/48374921 來自《LiaBin的部落格》
單例模式很常見,在面試中也會經常直接讓你寫一個單例出來
單例模式寫法一般分為兩種,懶漢式和餓漢式
餓漢式
public class SingleTon { //載入類的時候會初始化static的instance,從這以後,這個static的instance物件便一直佔著這段記憶體,永遠不會被回收掉。 private static SingleTon instance = new SingleTon(); //將建構函式private掉,避免直接new SingleTon() private SingleTon() { } /** * 因為是單例,所以只能通過static方法來獲取例項,因此必須是static的。 * 方法實現較為簡單,因為instance已經在載入類的時候被初始化好了,所以不存在多執行緒併發造成的問題 */ public static SingleTon getInstance() { return instance; } }
優點:
不需要考慮多執行緒問題,因為instance是靜態的,在類載入的時候就已經例項化了,同時也避免了synchronized所造成的效能問題,
缺點:
但這種方式也有點弊端,因為初始化類的時候就需要構造例項,(即便你還沒有用到這個例項),因此在某些特定條件下會耗費記憶體。
懶漢式
方式1: 基於volatile的雙重檢查鎖定的解決方案為什麼需要按如下這麼複雜的去實現,參考有詳細的解釋 http://blog.csdn.net/guolin_blog/article/details/8860649
這樣寫是沒問題的,因為私有建構函式中沒有初始化任何屬性,否則的話上面的程式碼還需要改進public class SingleTon { private static SingleTon instance = null; //將建構函式private掉,避免直接new SingleTon() private SingleTon() { } //synchronized避免多執行緒帶來的問題,但同時效率降低,另一方面採取多重鎖定,提高效率 public static SingleTon getInstance() { if (null == instance) { synchronized (SingleTon.class) { if (null == instance) { instance = new SingleTon(); } } } return instance; } }
這裡為什麼需要使用volatile關鍵字呢?public class SingleTon { private static volatile SingleTon instance = null; private String name; //將建構函式private掉,避免直接new SingleTon() private SingleTon() { name = "SingleTon"; } //synchronized避免多執行緒帶來的問題,但同時效率降低,另一方面採取多重鎖定,提高效率 public static SingleTon getInstance() { if (null == instance) { synchronized (SingleTon.class) { if (null == instance) { instance = new SingleTon(); } } } return instance; } }
假設執行緒thread1走到了第15行的if判斷髮現instance==null成立,於是都進入了外部的if體。這時候thread1先獲取了synchronized塊的鎖,於是thread1執行緒會執行第18行的instance = new SingleTon();這句程式碼,問題就出在這裡,這條語句它不是原子性執行的。在Java裡,例項化一個物件的過程簡單地講,可以分為兩步1)先為instance物件分配一塊記憶體,2)在這塊記憶體裡為instance物件裡的成員變數賦值(比如第11行裡為url賦值)。假設當thread1執行完第1)步而還沒有執行第2)步的時候,另外一個執行緒thread2走到了第15行,這時候instance已經不是null了,於是thread2直接返回了這個instance物件。有什麼問題呢?instance物件的初始化(變數賦值等操作)還沒執行完呢!thread2裡直接得到了一個沒有初始化完全的物件,就有可能導致很嚴重的問題了。
那麼volatile關鍵字有啥作用呢?當用volatile修飾了instance變數之後,對instance的寫操作”先行發生“於對它的讀操作。(這是Java虛擬機器裡的先行發生原則)這樣就保證了,thread1中的instance變數被完全初始化之後,thread2才能讀取它,當沒有完成初始化時,thread2只能等會兒啦。
優點:
需要的時候才去載入,記憶體消耗好一些。(因為在需要的時候才載入,所以叫懶漢式)
缺點:
程式碼如此複雜,,還有synchronized帶來的執行效率問題,呼叫同步方法會慢不少
方式2: 基於類初始化的解決方案
public class Singleton
{
private static class SingletonHolder
{
public final static Singleton instance = new Singleton();
}
public static Singleton getInstance()
{
return SingletonHolder.instance;
}
}
內部類的初始化是延遲的,外部類初始化時不會初始化內部類,只有在使用的時候才會初始化內部類。而Java語言規範規定,對於每一個類或介面C,都有一個唯一的初始化鎖LC與之對應。也就是說,SingletonHolder在各個執行緒初始化的時候是同步執行的,且全權由JVM承包了。
兩種延遲初始化方案總結
延遲初始化降低了初始化類或建立例項的開銷,但增加了訪問被延遲初始化的欄位的開銷。在大多數時候,正常的初始化要優於延遲初始化。如果確實需要對例項欄位使用執行緒安全的延遲初始化,請使用上面介紹的基於volatile的延遲初始化的方案;如果確實需要對靜態欄位使用執行緒安全的延遲初始化,請使用上面介紹的基於類初始化的方案。
總結
為了省麻煩,就用惡漢式吧。但是我一般用懶漢式,比較需要的時候才載入,可以節省記憶體。至於synchronized引起的效率問題,基本很少有這樣的場景,因為很少有兩個執行緒併發呼叫getInstance方法。