通常,爲了實現配置信息的實時更新,會有一個線程不停檢測配置文件或配置數據庫的內容,一旦發現變化,就更新到單例對象的屬性中。在更新這些信息的時候,很可能還會有其他線程正在讀取這些信息,造成意想不到的後果。還是以通過單例對象屬性停止線程服務爲例,如果更新屬性時讀寫不同步,可能訪問該屬性時這個屬性正好爲空(null),程序就會拋出異常。
有兩種方法:
1,參照讀者/寫者的處理方式
設置一個讀計數器,每次讀取配置信息前,將計數器加1,讀完後將計數器減1。只有在讀計數器爲0時,才能更新數據,同時要阻塞所有讀屬性的調用。代碼如下
public class GlobalConfig {
private static GlobalConfig instance;
private Vector properties = null;
private boolean isUpdating = false;
private int readCount = 0;
private GlobalConfig() {
//Load configuration information from DB or file
//Set values for properties
}
private static synchronized void syncInit() {
if (instance == null) {
instance = new GlobalConfig();
}
}
public static GlobalConfig getInstance() {
if (instance==null) {
syncInit();
}
return instance;
}
public synchronized void update(String p_data) {
syncUpdateIn();
//Update properties
}
private synchronized void syncUpdateIn() {
while (readCount > 0) {
try {
wait();
} catch (Exception e) {
}
}
}
private synchronized void syncReadIn() {
readCount++;
}
private synchronized void syncReadOut() {
readCount--;
notifyAll();
}
public Vector getProperties() {
syncReadIn();
//Process data
syncReadOut();
return properties;
}
}
2,採用"影子實例"的辦法
具體說,就是在更新屬性時,直接生成另一個單例對象實例,這個新生成的單例對象實例將從數據庫或文件中讀取最新的配置信息;然後將這些配置信息直接賦值給舊單例對象的屬性。如下面代碼所示。
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
}
private static synchronized void syncInit() {
if (instance = null) {
instance = new GlobalConfig();
}
}
public static GlobalConfig getInstance() {
if (instance = null) {
syncInit();
}
return instance;
}
public Vector getProperties() {
return properties;
}
public void updateProperties() {
//Load updated configuration information by new a GlobalConfig object
GlobalConfig shadow = new GlobalConfig();
properties = shadow.getProperties();
}
}
注意:在更新方法中,通過生成新的GlobalConfig的實例,從文件或數據庫中得到最新配置信息,並存放到properties屬性中。
上面兩個方法比較起來,第二個方法更好,首先,編程更簡單;其次,沒有那麼多的同步操作,對性能的影響也不大。