定義:確保一個類只有一個實例涵防,并提供對該實例的全局訪問,其構(gòu)造函數(shù)私有化吁讨。
單例模式的七種寫法
1、餓漢模式
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton(){
}
public static Singleton getInstance(){
return instance;
}
}
這種方式在類加載時就完成了初始化峦朗,所以類加載較慢建丧,但獲取對象的速度快。這種方式基于類加載機制波势,避免了線程的同步問題翎朱。在類加載的時候就完成實例化,沒有達到懶加載的效果尺铣。如果從始至終未使用過這個實例拴曲,則會造成內(nèi)存的浪費。
2凛忿、懶漢模式
public class Singleton {
private static Singleton instance;
private Singleton(){
}
public static Singleton getInstance(){
if (instance == null){
instance = new Singleton();
}
return instance;
}
}
這種懶漢模式聲明了一個靜態(tài)對象澈灼,在用戶第一次調(diào)用時初始化。這雖然節(jié)約了資源店溢,但第一次加載時需要實例化叁熔,反應(yīng)稍慢一些,主要是在多線程時不能正常工作床牧。
3荣回、懶漢模式(線程安全)
public class Singleton {
private static Singleton instance;
private Singleton(){
}
public static synchronized Singleton getInstance(){
if (instance == null){
instance = new Singleton();
}
return instance;
}
}
這種懶漢模式因為加了同步鎖,所以是線程安全的戈咳,但是每次調(diào)用getInstance方法時都需要進行同步心软,這會造成不必要的同步開銷革砸,而且大部分時候我們是用不到同步的,所以不建議用這種模式糯累。
4算利、雙重檢查模式(DCL)
public class Singleton {
private static volatile Singleton instance;
private Singleton(){
}
public static Singleton getInstance(){
if (instance == null){
synchronized (Singleton.class){
if (instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
這種寫法在getInstance方法中隊Singleton進行了兩次判空:第一次是為了不必要的同步,第二次是在Singleton等于null的情況下才創(chuàng)建實例泳姐。在這里使用volatile效拭,也會或多或少地影響性能,第一次加載時反應(yīng)稍慢胖秒,在高并發(fā)環(huán)境下也有一定的缺陷缎患。DCL雖然在一定程度上解決了資源的消耗和多余的同步、線程安全等問題阎肝,但其在某些情況下還是會出現(xiàn)失效的問題挤渔。
volatile作用:
(1)保證了不同線程對這個變量進行操作時的可見性,即一個線程修改了某個變量的值风题,這新值對其他線程來說是立即可見的判导。
(2)禁止指令重排序優(yōu)化。(什么是指令重排序:是指CPU采用了允許將多條指令不按程序規(guī)定的順序分開發(fā)送給各相應(yīng)電路單元處理)沛硅。
DCL失效:
當一個A線程執(zhí)行到instance = new Singleton()語句時眼刃,這里看起來是一句代碼,實際上這不是一個原子操作摇肌,這句代碼最終會被編譯成多條匯編指令擂红,它大致做了3件事:
(1)給Singleton的實例分配內(nèi)存;
(2)調(diào)用Singleton()的構(gòu)造函數(shù)围小,初始化成員字段昵骤;
(3)將instance對象指向分配的內(nèi)存空間(此時instance就不是null了)
但是,由于Java編譯器允許處理器亂序執(zhí)行肯适,以及JDK1.5之前JMM(Java Memory Model变秦,即java內(nèi)存模型)中Cache、寄存器到主內(nèi)存回寫順序的規(guī)定疹娶,上面的第二和第三的順序是無法保證的伴栓。也就是說伦连,執(zhí)行順序可能是1-2-3,也可能是1-3-2雨饺。如果是后者,并且在3執(zhí)行完畢惑淳、2未執(zhí)行之前额港,被切換到線程B上,這時候instance因為已經(jīng)在線程A內(nèi)執(zhí)行過了第三點歧焦,instance已經(jīng)是非空了移斩,所以肚医,線程B直接取走instance,再使用時就會出錯向瓷,這就是DCL失效問題肠套。
5、靜態(tài)內(nèi)部類單例模式
public class Singleton {
private Singleton() {
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
}
第一次加載Singleton是并不會初始化INSTANCE猖任,只有第一次調(diào)用getInstance方法是虛擬機加載SingletonHolder并初始化INSTANCE你稚。這樣不僅能確保線程安全,也能保證Singleton類的唯一性朱躺,所以是非常推薦的一種單例寫法刁赖。
6、枚舉單例模式
public enum Singleton {
INSTANCE;
public void doSomeThing() {
}
}
默認枚舉實例的創(chuàng)建時線程安全的长搀,并且在任何情況下都是單例宇弛。前面講過的機制單例模式,有一種情況會重寫創(chuàng)建對象源请,那就是反序列化:將一個單例實例對象寫到磁盤再讀取回來枪芒,從而獲得了一個實例。反序列化操作提供了readResolve方法谁尸,這個方法可以讓開發(fā)者控制對象的反序列化病苗。所以,如果要杜絕單例對象反序列化時重寫生成對象症汹,必須加入如下方法:
private Object readResolve() throws ObjectStreamException {
return SingletonHolder.INSTANCE;
}
枚舉單例的優(yōu)點就是簡單硫朦,不過Android使用enum之后的dex大小增加很多,運行時還會產(chǎn)生額外的內(nèi)存占用背镇,其可讀性也不高咬展,因此官方強烈建議不要在Android程序里面使用到enum,枚舉單例缺點也很明顯瞒斩。
7破婆、容器實現(xiàn)單例模式
/**
* 單例容器類
*/
public class SingletonManager {
private SingletonManager() {
}
private static Map<String, Object> instanceMap = new HashMap<>();
public static void registerInstance(String key, Object instance) {
if (!instanceMap.containsKey(key)) {
instanceMap.put(key, instance);
}
}
public static Object getInstance(String key) {
return instanceMap.get(key);
}
}
/**
* 單例模板
*/
public class SingletonPattern {
SingletonPattern() {
}
public void doSomething() {
Log.d("==", "doSomeing");
}
}
// 具體用法
SingletonManager.registerInstance("SingletonPattern", new SingletonPattern());
SingletonPattern singletonPattern = (SingletonPattern) SingletonManager.getInstance("SingletonPattern");
singletonPattern.doSomething();
這種方式,根據(jù)key獲取對象對應(yīng)類型的對象胸囱,隱藏了具體實現(xiàn)祷舀,降低了耦合度。