概念:
Java中單例模式是一種常見的設計模式,單例模式的寫法有好幾種,這裏主要介紹三種:懶漢式單例、餓漢式單例、登記式單例。
單例模式有如下特色:
一、單例類只能有一個實例。
二、單例類必須本身建立本身的惟一實例。
三、單例類必須給全部其餘對象提供這一實例。
單例模式確保某個類只有一個實例,並且自行實例化並向整個系統提供這個實例。在計算機系統中,線程池、緩存、日誌對象、對話框、打印機、顯卡的驅動程序對象常被設計成單例。這些應用都或多或少具備資源管理器的功能。每臺計算機能夠有若干個打印機,但只能有一個Printer Spooler,以免兩個打印做業同時輸出到打印機中。每臺計算機能夠有若干通訊端口,系統應當集中管理這些通訊端口,以免一個通訊端口同時被兩個請求同時調用。總之,選擇單例模式就是爲了不不一致狀態,避免政出多頭。java
1、懶漢式單例 設計模式
package com.test.singleton; /** * <p class="detail"> * 功能:懶漢模式測試 * </p> * @ClassName: LanghanTest * @version V1.0 * @date 2016年11月25日 * @author damowang */ public class LanghanTest { /** * * <p class="detail"> * 功能:這裏寫描述 * </p> * */ private LanghanTest(){}; private static LanghanTest langhan=null; /** * <p class="detail"> * 功能:Singleton經過將構造方法限定爲private避免了類在外部被實例化, * 在同一個虛擬機範圍內,Singleton的惟一實例只能經過getInstance()方法訪問。 * </p> * @author damowang * @return * @throws */ public static LanghanTest getInstance(){ if(langhan==null){ langhan = new LanghanTest(); } return langhan; } }
LanghanTest經過將構造方法限定爲private避免了類在外部被實例化,在同一個虛擬機範圍內,LanghanTest的惟一實例只能經過getInstance()方法訪問。 (以上懶漢線程不安全,文章尾部提供線程安全方式)緩存
2、餓漢式單例安全
package com.test.singleton; /** * <p class="detail"> * 功能:餓漢似單例 :在類初始化時,已經自行實例化,且 final 修飾(靜態的), * 因此天生是線程安全的,永遠保持惟一 * </p> * @ClassName: EhanTest * @version V1.0 * @date 2016年11月25日 * @author damowang */ public class EhanTest { private EhanTest(){}; private static final EhanTest single = new EhanTest(); /** * <p class="detail"> * 功能:獲取實例 * </p> * @author damowang * @return * @throws */ public static EhanTest getInstance() { return single; } }
餓漢式在類建立的同時就已經建立好一個靜態的對象供系統使用,之後再也不改變,因此天生是線程安全的。多線程
3、登記式單例性能
package com.test.singleton; import java.util.HashMap; import java.util.Map; /** * <p class="detail"> * 功能:登記式單例實際上維護了一組單例類的實例, 將這些實例存放在一個Map(登記薄)中 * ,對於已經登記過的實例,則從Map直接返回,對於沒有登記的,則先登記,而後返回。 * </p> * @ClassName: DengjiTest * @version V1.0 * @date 2016年11月25日 * @author damowang */ public class DengjiTest { private static Map<String,DengjiTest> map = new HashMap<String,DengjiTest>(); static{ DengjiTest single = new DengjiTest(); map.put(single.getClass().getName(), single); } //保護的默認構造 protected DengjiTest(){} //靜態工廠方法,返還此類唯一的實例 public static DengjiTest getInstance(String name) { if(name == null) { name = DengjiTest.class.getName(); System.out.println("name == null"+"--->name="+name); } if(map.get(name) == null) { try { map.put(name, (DengjiTest) Class.forName(name).newInstance()); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } return map.get(name); } //一個示意性的商業方法 public String about() { return "Hello, I am RegSingleton."; } public static void main(String[] args) { DengjiTest single3 = DengjiTest.getInstance("damowang"); if(single3==null){ System.out.println("未獲取到實例"); return; } System.out.println(single3.about()); } }
用的比較少,另外其實內部實現仍是用的餓漢式單例,由於其中的static方法塊,它的單例在類被裝載的時候就被實例化了。測試
懶漢線程安全this
在getInstance方法上加同步.net
public static synchronized LanghanTest getInstance(){ if(langhan==null){ langhan = new LanghanTest(); } return langhan; }
雙重檢查鎖定線程
public static LanghanTest getInstance(){ if (langhan == null) { synchronized (LanghanTest.class) { if (langhan == null) { langhan = new LanghanTest(); } } } return langhan; }
靜態內部類
private static class LazyHolder { private static final LanghanTest INSTANCE = new LanghanTest(); } private LanghanTest (){} /** * <p class="detail"> * 功能:獲取實例 * </p> * @author damowang * @return * @throws */ public static final LanghanTest getInstance() { return LazyHolder.INSTANCE; }
這種比上面一、2都好一些,既實現了線程安全,又避免了同步帶來的性能影響。
第1種,在方法調用上加了同步,雖然線程安全了,可是每次都要同步,會影響性能,畢竟99%的狀況下是不須要同步的,
第2種,在getInstance中作了兩次null檢查,確保了只有第一次調用單例的時候纔會作同步,這樣也是線程安全的,同時避免了每次都同步的性能損耗
第3種,利用了classloader的機制來保證初始化instance時只有一個線程,因此也是線程安全的,同時沒有性能損耗,因此通常我傾向於使用這一種。
餓漢式和懶漢式區別
從名字上來講,餓漢和懶漢,
餓漢就是類一旦加載,就把單例初始化完成,保證getInstance的時候,單例是已經存在的了,
而懶漢比較懶,只有當調用getInstance的時候,纔回去初始化這個單例。
另外從如下兩點再區分如下這兩種方式:
餓漢式天生就是線程安全的,能夠直接用於多線程而不會出現問題,
懶漢式自己是非線程安全的,爲了實現線程安全有幾種寫法,分別是上面的一、二、3,這三種實如今資源加載和性能方面有些區別。
餓漢式在類建立的同時就實例化一個靜態對象出來,無論以後會不會使用這個單例,都會佔據必定的內存,可是相應的,在第一次調用時速度也會更快,由於其資源已經初始化完成,
而懶漢式顧名思義,會延遲加載,在第一次使用該單例的時候纔會實例化對象出來,第一次調用時要作初始化,若是要作的工做比較多,性能上會有些延遲,以後就和餓漢式同樣了。
什麼是線程安全?
若是你的代碼所在的進程中有多個線程在同時運行,而這些線程可能會同時運行這段代碼。若是每次運行結果和單線程運行的結果是同樣的,並且其餘的變量的值也和預期的是同樣的,就是線程安全的。
或者說:一個類或者程序所提供的接口對於線程來講是原子操做,或者多個線程之間的切換不會致使該接口的執行結果存在二義性,也就是說咱們不用考慮同步的問題,那就是線程安全的。
應用
如下是一個單例類使用的例子,以懶漢式爲例,這裏爲了保證線程安全,使用了靜態內部類的方式:
package com.test.singleton; /** * <p class="detail"> * 功能:懶漢模式測試 * </p> * @ClassName: LanghanTest * @version V1.0 * @date 2016年11月25日 * @author damowang */ public class LanghanTest { String name = null; private static class LazyHolder { private static final LanghanTest INSTANCE = new LanghanTest(); } private LanghanTest (){} /** * <p class="detail"> * 功能:獲取實例 * </p> * @author damowang * @return * @throws */ public static final LanghanTest getInstance() { return LazyHolder.INSTANCE; } public final String getName() { return name; } public final void setName(String name) { this.name = name; } public void printInfo() { System.out.println("the name is " + name); } }
package com.test.singleton; public class TestMain { public static void main(String[] args) { LanghanTest damowang = LanghanTest.getInstance(); damowang.setName("大魔王"); LanghanTest bruce = LanghanTest.getInstance(); bruce.setName("bruce"); System.out.println("damowang: "+damowang.getName()); System.out.println("bruce: "+bruce.getName()); if(damowang == bruce){ System.out.println("建立的是同一個實例"); }else{ System.out.println("建立的不是同一個實例"); } } }
輸出結果
damowang: bruce bruce: bruce 建立的是同一個實例
結論:由結果能夠得知單例模式爲一個面向對象的應用程序提供了對象唯一的訪問點,無論它實現何種功能,整個應用程序都會同享一個實例對象。
對於單例模式的幾種實現方式,知道餓漢式和懶漢式的區別,線程安全,資源加載的時機,還有懶漢式爲了實現線程安全的3種方式的細微差異。