天天看點

Java單态模式

Java的單态模式 Singletom

作用:保證在Java應用程式中,一個Java類隻有一個執行個體存在;是以一般單态類會提供一個傳回該類執行個體的方法。提供一個對對象的全局通路指針。

優點:節省記憶體,限制類的個數,有利于Java的垃圾回收機制(Garbage Collection );

缺點及注意點:

         1、多線程情況下,懶加載模式可能導緻線程不安全因素,例如:同時有兩個線程同時調用getInstance方法擷取執行個體時,可能兩個線程同時進入if語句判斷塊,此時類尚未被執行個體化,那麼将同時得到兩個不同的執行個體(此注意點比較容易避免,擷取執行個體時使用同步sync就可以很好的解決)。

         2、當單例類被多個classloader加載的情況下,可能獲得多個單例類的執行個體(此種情況可能比較難避免,這需要使所有的類使用相同的類加載器加載)。

         3、當單例類實作了序列化接口(Serializer)時,我們如果将對象序列化,并反序列化得到執行個體時,這個執行個體将是一個新的執行個體,而不是序列化之前的執行個體(在這種情況下,需要在此類中添加readResolve方法,将傳回對象設定為目前執行個體,否則會獲得一個不同意序列化之前的類)。

單例模式具體分為餓漢式、懶漢式及登記式(這個很少介紹):

1、餓漢式   ~~ HungrySingleton.java ~~

package org.effine.singleton;

public class HungrySingleton {

public static HungrySingleton hungry = new HungrySingleton();

private HungrySingleton() {

}

public static final HungrySingleton getInstance() {

return hungry;

}

}

2、懶漢式  ~~LazySingleton.java~~

package org.effine.singleton;

public class LazySingleton {

public static LazySingleton lazy = null;

private LazySingleton() {

}

public static LazySingleton getInstance() {

if (lazy == null) {

synchronized (LazySingleton.class) {

lazy = new LazySingleton();

}

}

return lazy;

}

}

引用文章針對上面代碼(lazy=new LazySingleton();)有加上一次if判斷(if(lazy==null) ),給出的理由是“采用雙重判斷,提高了多線程下操作的效率”;我認為沒能提高效率,是以去掉多餘的if判斷;(ps:如有見解,歡迎指正探讨)

3、登記式 ~~ RegisterSingleton.java ~~

package org.effine.singleton;

import java.util.HashMap;

import java.util.Map;

public class RegisterSingleton {

private static Map<String, RegisterSingleton> map = new HashMap<String, RegisterSingleton>();

static {

RegisterSingleton register = new RegisterSingleton();

map.put(register.getClass().getName(), register);

}

private RegisterSingleton() {

}

public static RegisterSingleton getInstance(String register) {

if (register == null) {

register = RegisterSingleton.class.getName();

}

if (map.get(register) == null) {

try {

map.put(register, (RegisterSingleton) Class.forName(register)

.newInstance());

} catch (InstantiationException e) {

e.printStackTrace();

} catch (IllegalAccessException e) {

e.printStackTrace();

} catch (ClassNotFoundException e) {

e.printStackTrace();

}

}

return map.get(register);

}

}

分析餓漢式和懶漢式:

    餓漢式是以預先加載方式加載對象;不管是單線程還是多線程都是線程安全的;懶漢式則以延遲加載方式加載對象;單線程沒有任何問題,如果是多線程則可能發生線程不安全,需要加上“互斥鎖”;

    在餓漢式中該類的靜态方法getInstance()被調用之前,該類的對象已經存在,假設現有兩個線程t1 \ t2在調用該方法擷取該類的對象,t1 \ t2 無論怎麼調用都不會産生新的對象,因為該對象在方法調用之前已經存在,是伴随類的生成而生成。相反,懶漢式對象調用方法getInstance()之前對象不一定存在(至少第一次調用該方法時對象不存在),是以他每次調用之前要檢測,若對象不存在則生成對象,對象存在則直接傳回。在多線程環境下問題就出現生成對象的時候,假設有兩個線程(t1,t2)在調用該方法,考慮這種情況,就是當線程t1檢測到該對象不存在的時候,正要準備建立線程(還沒建立),而在這個時候CPU切換到t2上執行權交給t2,t2檢測到該對象不存在,就生成了對象,t2線程結束了,執行權回到t1上,t1這個時候就不需要判斷了,因為開始他判斷了的,他就直接向下執行,生成了對象。這樣就導緻了該類生成了兩個不同的對象,當然這種情況不一定會發生,但這種情況是絕對存在的,也許你運作10000次都不會出現,或許你一運作就會出現該問題。

     所有我們需要對多線程通路的臨界資源getInstance()方法加“互斥鎖”,保證臨界資源線程安全;這表明同一時刻隻有一個線程持有該互斥鎖,其他線程若要獲得該互斥鎖通路對象需要等待該線程(持有互斥鎖的對象)将其釋放。對臨界資源getInstance()方法加上互斥鎖即需要使用關鍵字synchronized,如我們使用synchronized修飾方法getInstance(),則每次調用該方法時都需要檢測鎖,效率不高;其實我們可以這樣思考隻有在對象被建立的時候我們才上鎖,保證對象的唯一,一旦對象都存在了,以後操作還需要鎖嘛,不需要了吧,這樣我們并得到上面的懶漢式單例模式。

總結:

  餓漢式:對象預先加載,線程是安全的,在類建立好的同時對象生成,調用獲得對象執行個體的方法反應速度快,代碼簡練。

  懶漢式:對象延遲加載,效率高,隻有在使用的時候才執行個體化對象,但若設計不當線程會不安全,代碼相對于餓漢式複雜,第一次加載類對象的時候反應不快。

Reference :http://hi.baidu.com/chenbobio/item/9c4ecfa95144fb7b6cd455ca