尋找一種性能改進(jìn)方法時(shí),你可能會(huì)選擇像下面這樣重寫getInstance()方法:
- public static Singleton getInstance() {
- if(singleton == null) {
- synchronized(Singleton.class) {
- singleton = new Singleton();
- }
- }
- return singleton;
- }
這個(gè)代碼片段只同步了關(guān)鍵的代碼,而不是同步整個(gè)方法。然而這段代碼卻不是線程安全的。考慮一下下面的假定:線程1進(jìn)入同步塊,并且在它給singleton成員變量賦值之前線程1被切換。接著另一個(gè)線程進(jìn)入if塊。第二個(gè)線程將等待直到第一個(gè)線程完成,并且仍然會(huì)得到兩個(gè)不同的單例類實(shí)例。有修復(fù)這個(gè)問題的方法嗎?請(qǐng)讀下去。
雙重加鎖檢查
初看上去,雙重加鎖檢查似乎是一種使懶漢式實(shí)例化為線程安全的技術(shù)。下面的代碼片段展示了這種技術(shù):
- public static Singleton getInstance() {
- if(singleton == null) {
- synchronized(Singleton.class) {
- if(singleton == null) {
- singleton = new Singleton();
- }
- }
- }
- return singleton;
- }
如果兩個(gè)線程同時(shí)訪問getInstance()方法會(huì)發(fā)生什么?想像一下線程1進(jìn)行同步塊馬上又被切換。接著,第二個(gè)線程進(jìn)入if 塊。當(dāng)線程1退出同步塊時(shí),線程2會(huì)重新檢查看是否singleton實(shí)例仍然為null。因?yàn)榫€程1設(shè)置了singleton成員變量,所以線程2的第二次檢查會(huì)失敗,第二個(gè)單例類實(shí)例也就不會(huì)被創(chuàng)建。似乎就是如此。
不幸的是,雙重加鎖檢查不會(huì)保證正常工作,因?yàn)榫幾g器會(huì)在Singleton的構(gòu)造方法被調(diào)用之前隨意給singleton賦一個(gè)值。如果在singleton引用被賦值之后而被初始化之前線程1被切換,線程2就會(huì)被返回一個(gè)對(duì)未初始化的單例類實(shí)例的引用。
一個(gè)改進(jìn)的線程安全的單例模式實(shí)現(xiàn)
例7列出了一個(gè)簡(jiǎn)單、快速而又是線程安全的單例模式實(shí)現(xiàn):
例7.一個(gè)簡(jiǎn)單的單例類
- public class Singleton {
- public final static Singleton INSTANCE = new Singleton();
- private Singleton() {
- // Exists only to defeat instantiation.
- }
- }
這段代碼是線程安全的是因?yàn)殪o態(tài)成員變量一定會(huì)在類被第一次訪問時(shí)被創(chuàng)建。你得到了一個(gè)自動(dòng)使用了懶漢式實(shí)例化的線程安全的實(shí)現(xiàn);你應(yīng)該這樣使用它:
- Singleton singleton = Singleton.INSTANCE;
- singleton.dothis();
- singleton.dothat();
- ...
當(dāng)然萬事并不完美,前面的Singleton只是一個(gè)折衷的方案;如果你使用那個(gè)實(shí)現(xiàn),你就無法改變它以便后來你可能想要允許多個(gè)單例類的實(shí)例。用一種更折哀的單例模式實(shí)現(xiàn)(通過一個(gè)getInstance()方法獲得實(shí)例)你可以改變這個(gè)方法以便返回一個(gè)唯一的實(shí)例或者是數(shù)百個(gè)實(shí)例中的一個(gè).你不能用一個(gè)公開且是靜態(tài)的(public static)成員變量這樣做.
你可以安全的使用例7的單例模式實(shí)現(xiàn)或者是例1的帶一個(gè)同步的getInstance()方法的實(shí)現(xiàn).然而,我們必須要研究另一個(gè)問題:你必須在編譯期指定這個(gè)單例類,這樣就不是很靈活.一個(gè)單例類的注冊(cè)表會(huì)讓我們?cè)谶\(yùn)行期指定一個(gè)單例類.
使用注冊(cè)表
使用一個(gè)單例類注冊(cè)表可以:
在運(yùn)行期指定單例類
防止產(chǎn)生多個(gè)單例類子類的實(shí)例
在例8的單例類中,保持了一個(gè)通過類名進(jìn)行注冊(cè)的單例類注冊(cè)表:
例8 帶注冊(cè)表的單例類
- import java.util.HashMap;
- import org.apache.log4j.Logger;
- public class Singleton {
- private static HashMap map = new HashMap();
- private static Logger logger = Logger.getRootLogger();
- protected Singleton() {
- // Exists only to thwart instantiation
- }
- public static synchronized Singleton getInstance(String classname) {
- if(classname == null) throw new IllegalArgumentException("Illegal classname");
- Singleton singleton = (Singleton)map.get(classname);
- if(singleton != null) {
- logger.info("got singleton from map: " + singleton);
- return singleton;
- }
- if(classname.equals("SingeltonSubclass_One"))
- singleton = new SingletonSubclass_One();
- else if(classname.equals("SingeltonSubclass_Two"))
- singleton = new SingletonSubclass_Two();
- map.put(classname, singleton);
- logger.info("created singleton: " + singleton);
- return singleton;
- }
- // Assume functionality follows that's attractive to inherit
- }
這段代碼的基類首先創(chuàng)建出子類的實(shí)例,然后把它們存儲(chǔ)在一個(gè)Map中。但是基類卻得付出很高的代價(jià)因?yàn)槟惚仨殲槊恳粋€(gè)子類替換它的getInstance()方法。幸運(yùn)的是我們可以使用反射處理這個(gè)問題。
使用反射
在例9的帶注冊(cè)表的單例類中,使用反射來實(shí)例化一個(gè)特殊的類的對(duì)象。與例8相對(duì)的是通過這種實(shí)現(xiàn),Singleton.getInstance()方法不需要在每個(gè)被實(shí)現(xiàn)的子類中重寫了。
例9 使用反射實(shí)例化單例類
- import java.util.HashMap;
- import org.apache.log4j.Logger;
- public class Singleton {
- private static HashMap map = new HashMap();
- private static Logger logger = Logger.getRootLogger();
- protected Singleton() {
- // Exists only to thwart instantiation
- }
- public static synchronized Singleton getInstance(String classname) {
- Singleton singleton = (Singleton)map.get(classname);
- if(singleton != null) {
- logger.info("got singleton from map: " + singleton);
- return singleton;
- }
- try {
- singleton = (Singleton)Class.forName(classname).newInstance();
- }
- catch(ClassNotFoundException cnf) {
- logger.fatal("Couldn't find class " + classname);
- }
- catch(InstantiationException ie) {
- logger.fatal("Couldn't instantiate an object of type " + classname);
- }
- catch(IllegalAccessException ia) {
- logger.fatal("Couldn't access class " + classname);
- }
- map.put(classname, singleton);
- logger.info("created singleton: " + singleton);
- return singleton;
- }
- }
關(guān)于單例類的注冊(cè)表應(yīng)該說明的是:它們應(yīng)該被封裝在它們自己的類中以便最大限度的進(jìn)行復(fù)用。
封裝注冊(cè)表
例10列出了一個(gè)單例注冊(cè)表類。
例10 一個(gè)SingletonRegistry類
- import java.util.HashMap;
- import org.apache.log4j.Logger;
- public class SingletonRegistry {
- public static SingletonRegistry REGISTRY = new SingletonRegistry();
- private static HashMap map = new HashMap();
- private static Logger logger = Logger.getRootLogger();
- protected SingletonRegistry() {
- // Exists to defeat instantiation
- }
- public static synchronized Object getInstance(String classname) {
- Object singleton = map.get(classname);
- if(singleton != null) {
- return singleton;
- }
- try {
- singleton = Class.forName(classname).newInstance();
- logger.info("created singleton: " + singleton);
- }
- catch(ClassNotFoundException cnf) {
- logger.fatal("Couldn't find class " + classname);
- }
- catch(InstantiationException ie) {
- logger.fatal("Couldn't instantiate an object of type " +
- classname);
- }
- catch(IllegalAccessException ia) {
- logger.fatal("Couldn't access class " + classname);
- }
- map.put(classname, singleton);
- return singleton;
- }
- }
注意我是把SingletonRegistry類作為一個(gè)單例模式實(shí)現(xiàn)的。我也通用化了這個(gè)注冊(cè)表以便它能存儲(chǔ)和取回任何類型的對(duì)象。例11顯示了的Singleton類使用了這個(gè)注冊(cè)表。
例11 使用了一個(gè)封裝的注冊(cè)表的Singleton類
- import java.util.HashMap;
- import org.apache.log4j.Logger;
- public class Singleton {
- protected Singleton() {
- // Exists only to thwart instantiation.
- }
- public static Singleton getInstance() {
- return (Singleton)SingletonRegistry.REGISTRY.getInstance(classname);
- }
- }
上面的Singleton類使用那個(gè)注冊(cè)表的唯一實(shí)例通過類名取得單例對(duì)象。
現(xiàn)在我們已經(jīng)知道如何實(shí)現(xiàn)線程安全的單例類和如何使用一個(gè)注冊(cè)表去在運(yùn)行期指定單例類名,接著讓我們考查一下如何安排類載入器和處理序列化。