Java單例模式(1)
單例對(duì)象(Singleton)是一種常用的設(shè)計(jì)模式。在Java應(yīng)用中,單例對(duì)象能保證在一個(gè)JVM中,該對(duì)象只有一個(gè)實(shí)例存在。正是由于這個(gè)特 點(diǎn),單例對(duì)象通常作為程序中的存放配置信息的載體,因?yàn)樗鼙WC其他對(duì)象讀到一致的信息。例如在某個(gè)服務(wù)器程序中,該服務(wù)器的配置信息可能存放在數(shù)據(jù)庫(kù)或 文件中,這些配置數(shù)據(jù)由某個(gè)單例對(duì)象統(tǒng)一讀取,服務(wù)進(jìn)程中的其他對(duì)象如果要獲取這些配置信息,只需訪問(wèn)該單例對(duì)象即可。這種方式極大地簡(jiǎn)化了在復(fù)雜環(huán)境 下,尤其是多線程環(huán)境下的配置管理,但是隨著應(yīng)用場(chǎng)景的不同,也可能帶來(lái)一些同步問(wèn)題。本文將探討一下在多線程環(huán)境下,使用單例對(duì)象作配置信息管理時(shí)可能會(huì)帶來(lái)的幾個(gè)同步問(wèn)題,并針對(duì)每個(gè)問(wèn)題給出可選的解決辦法。
問(wèn)題描述
在多線程環(huán)境下,單例對(duì)象的同步問(wèn)題主要體現(xiàn)在兩個(gè)方面,單例對(duì)象的初始化和單例對(duì)象的屬性更新。
本文描述的方法有如下假設(shè):
1. 單例對(duì)象的屬性(或成員變量)的獲取,是通過(guò)單例對(duì)象的初始化實(shí)現(xiàn)的。也就是說(shuō),在單例對(duì)象初始化時(shí),會(huì)從文件或數(shù)據(jù)庫(kù)中讀取最新的配置信息。
2. 其他對(duì)象不能直接改變單例對(duì)象的屬性,單例對(duì)象屬性的變化來(lái)源于配置文件或配置數(shù)據(jù)庫(kù)數(shù)據(jù)的變化。
1.1 單例對(duì)象的初始化
首先,討論一下單例對(duì)象的初始化同步。單例模式的通常處理方式是,在對(duì)象中有一個(gè)靜態(tài)成員變量,其類型就是單例類型本身;如果該變量為null,則創(chuàng)建該單例類型的對(duì)象,并將該變量指向這個(gè)對(duì)象;如果該變量不為null,則直接使用該變量。
其過(guò)程如下面代碼所示:
public class GlobalConfig {
private static GlobalConfig instance = null;
private Vector properties = null;
private GlobalConfig() {
//Load configuration information from DB or file
//Set values for properties
}
public static GlobalConfig getInstance() {
if (instance == null) {
instance = new GlobalConfig();
}
return instance;
}
public Vector getProperties() {
return properties;
}
}
這種處理方式在單線程的模式下可以很好的運(yùn)行;但是在多線程模式下,可能產(chǎn)生問(wèn)題。如果第一個(gè)線程發(fā)現(xiàn)成員變量為null,準(zhǔn)備創(chuàng)建對(duì)象;這是第二 個(gè)線程同時(shí)也發(fā)現(xiàn)成員變量為null,也會(huì)創(chuàng)建新對(duì)象。這就會(huì)造成在一個(gè)JVM中有多個(gè)單例類型的實(shí)例。如果這個(gè)單例類型的成員變量在運(yùn)行過(guò)程中變化,會(huì) 造成多個(gè)單例類型實(shí)例的不一致,產(chǎn)生一些很奇怪的現(xiàn)象。例如,某服務(wù)進(jìn)程通過(guò)檢查單例對(duì)象的某個(gè)屬性來(lái)停止多個(gè)線程服務(wù),如果存在多個(gè)單例對(duì)象的實(shí)例,就 會(huì)造成部分線程服務(wù)停止,部分線程服務(wù)不能停止的情況。
1.2 單例對(duì)象的屬性更新
通常,為了實(shí)現(xiàn)配置信息的實(shí)時(shí)更新,會(huì)有一個(gè)線程不停檢測(cè)配置文件或配置數(shù)據(jù)庫(kù)的內(nèi)容,一旦發(fā)現(xiàn)變化,就更新到單例對(duì)象的屬性中。在更新這些信 息的時(shí)候,很可能還會(huì)有其他線程正在讀取這些信息,造成意想不到的后果。還是以通過(guò)單例對(duì)象屬性停止線程服務(wù)為例,如果更新屬性時(shí)讀寫(xiě)不同步,可能訪問(wèn)該 屬性時(shí)這個(gè)屬性正好為空(null),程序就會(huì)拋出異常。
解決方法
2.1 單例對(duì)象的初始化同步
對(duì)于初始化的同步,可以通過(guò)如下代碼所采用的方式解決。
public class GlobalConfig { |
這種處理方式雖然引入了同步代碼,但是因?yàn)檫@段同步代碼只會(huì)在最開(kāi)始的時(shí)候執(zhí)行一次或多次,所以對(duì)整個(gè)系統(tǒng)的性能不會(huì)有影響。
2.2 單例對(duì)象的屬性更新同步為了解決第2個(gè)問(wèn)題,有兩種方法:
1,參照讀者/寫(xiě)者的處理方式
設(shè)置一個(gè)讀計(jì)數(shù)器,每次讀取配置信息前,將計(jì)數(shù)器加1,讀完后將計(jì)數(shù)器減1.只有在讀計(jì)數(shù)器為0時(shí),才能更新數(shù)據(jù),同時(shí)要阻塞所有讀屬性的調(diào)用。代碼如下。
public class GlobalConfig { |
2,采用"影子實(shí)例"的辦法
具體說(shuō),就是在更新屬性時(shí),直接生成另一個(gè)單例對(duì)象實(shí)例,這個(gè)新生成的單例對(duì)象實(shí)例將從數(shù)據(jù)庫(kù)或文件中讀取最新的配置信息;然后將這些配置信息直接賦值給舊單例對(duì)象的屬性。如下面代碼所示。
public class GlobalConfig { |
注意:在更新方法中,通過(guò)生成新的GlobalConfig的實(shí)例,從文件或數(shù)據(jù)庫(kù)中得到最新配置信息,并存放到properties屬性中。
上面兩個(gè)方法比較起來(lái),第二個(gè)方法更好,首先,編程更簡(jiǎn)單;其次,沒(méi)有那么多的同步操作,對(duì)性能的影響也不大。
posted on 2008-06-05 12:43 yunye 閱讀(300) 評(píng)論(0) 編輯 收藏 所屬分類: 其他