熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> Java編程 >> Java高級技術 >> 正文

Java單例對象同步問題探討

2022-06-13   來源: Java高級技術 

  單例對象(Singleton)是一種常用的設計模式在Java應用中單例對象能保證在一個JVM中該對象只有一個實例存在正是由於這個特點單例對象通常作為程序中的存放配置信息的載體因為它能保證其他對象讀到一致的信息例如在某個服務器程序中該服務器的配置信息可能存放在數據庫或文件中這些配置數據由某個單例對象統一讀取服務進程中的其他對象如果要獲取這些配置信息只需訪問該單例對象即可這種方式極大地簡化了在復雜環境下尤其是多線程環境下的配置管理但是隨著應用場景的不同也可能帶來一些同步問題

  本文將探討一下在多線程環境下使用單例對象作配置信息管理時可能會帶來的幾個同步問題並針對每個問題給出可選的解決辦法

  問題描述

  在多線程環境下單例對象的同步問題主要體現在兩個方面單例對象的初始化和單例對象的屬性更新

  本文描述的方法有如下假設

   單例對象的屬性(或成員變量)的獲取是通過單例對象的初始化實現的也就是說在單例對象初始化時會從文件或數據庫中讀取最新的配置信息

   其他對象不能直接改變單例對象的屬性單例對象屬性的變化來源於配置文件或配置數據庫數據的變化

   單例對象的初始化

  首先討論一下單例對象的初始化同步單例模式的通常處理方式是在對象中有一個靜態成員變量其類型就是單例類型本身如果該變量為null則創建該單例類型的對象並將該變量指向這個對象如果該變量不為null則直接使用該變量

  其過程如下面代碼所示

  

  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;
}
}

  這種處理方式在單線程的模式下可以很好的運行但是在多線程模式下可能產生問題如果第一個線程發現成員變量為null准備創建對象這是第二個線程同時也發現成員變量為null也會創建新對象這就會造成在一個JVM中有多個單例類型的實例如果這個單例類型的成員變量在運行過程中變化會造成多個單例類型實例的不一致產生一些很奇怪的現象例如某服務進程通過檢查單例對象的某個屬性來停止多個線程服務如果存在多個單例對象的實例就會造成部分線程服務停止部分線程服務不能停止的情況

   單例對象的屬性更新

  通常為了實現配置信息的實時更新會有一個線程不停檢測配置文件或配置數據庫的內容一旦發現變化就更新到單例對象的屬性中在更新這些信息的時候很可能還會有其他線程正在讀取這些信息造成意想不到的後果還是以通過單例對象屬性停止線程服務為例如果更新屬性時讀寫不同步可能訪問該屬性時這個屬性正好為空(null)程序就會拋出異常

  解決方法

   單例對象的初始化同步

  對於初始化的同步可以通過如下代碼所采用的方式解決

  

  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 class GlobalConfig {
private static GlobalConfig instance;
private Vector properties = null;
private boolean isUpdating = false;
private int readCount = ;
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 > ) {
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;
}
}

  采用影子實例的辦法

  具體說就是在更新屬性時直接生成另一個單例對象實例這個新生成的單例對象實例將從數據庫或文件中讀取最新的配置信息然後將這些配置信息直接賦值給舊單例對象的屬性如下面代碼所示

  

  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 = shadowgetProperties();
}
}

  注意在更新方法中通過生成新的GlobalConfig的實例從文件或數據庫中得到最新配置信息並存放到properties屬性中

  上面兩個方法比較起來第二個方法更好首先編程更簡單其次沒有那麼多的同步操作對性能的影響也不大


From:http://tw.wingwit.com/Article/program/Java/gj/201311/27547.html
    推薦文章
    Copyright © 2005-2022 電腦知識網 Computer Knowledge   All rights reserved.