2009/07/23 16:12
单例对象(Singleton)是一种常用的设计模式。在Java应用中,单例对象能保证在一个JVM中,该对象只有一个实例存在。正是由于这个特点,单例对象通常作为程序中的存放配置信息的载体,因为它能保证其他对象读到一致的信息。例如在某个服务器程序中,该服务器的配置信息可能存放在数据库或文件中,这些配置数据由某个单例对象统一读取,服务进程中的其他对象如果要获取这些配置信息,只需访问该单例对象即可。这种方式极大地简化了在复杂环境下,尤其是多线程环境下的配置管理,但是随着应用场景的不同,也可能带来一些同步问题。
本文将探讨一下在多线程环境下,使用单例对象作配置信息管理时可能会带来的几个同步问题,并针对每个问题给出可选的解决办法。
问题描述
在多线程环境下,单例对象的同步问题主要体现在两个方面,单例对象的初始化和单例对象的属性更新。
本文描述的方法有如下假设:
1. 单例对象的属性(或成员变量)的获取,是通过单例对象的初始化实现的。也就是说,在单例对象初始化时,会从文件或数据库中读取最新的配置信息。
2. 其他对象不能直接改变单例对象的属性,单例对象属性的变化来源于配置文件或配置数据库数据的变化。
1.1 单例对象的初始化
首先,讨论一下单例对象的初始化同步。单例模式的通常处理方式是,在对象中有一个静态成员变量,其类型就是单例类型本身;如果该变量为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中有多个单例类型的实例。如果这个单例类型的成员变量在运行过程中变化,会造成多个单例类型实例的不一致,产生一些很奇怪的现象。例如,某服务进程通过检查单例对象的某个属性来停止多个线程服务,如果存在多个单例对象的实例,就会造成部分线程服务停止,部分线程服务不能停止的情况。
1.2 单例对象的属性更新
通常,为了实现配置信息的实时更新,会有一个线程不停检测配置文件或配置数据库的内容,一旦发现变化,就更新到单例对象的属性中。在更新这些信息的时候,很可能还会有其他线程正在读取这些信息,造成意想不到的后果。还是以通过单例对象属性停止线程服务为例,如果更新属性时读写不同步,可能访问该属性时这个属性正好为空(null),程序就会抛出异常。
解决方法
2.1 单例对象的初始化同步
对于初始化的同步,可以通过如下代码所采用的方式解决。
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;
}
}
这种处理方式虽然引入了同步代码,但是因为这段同步代码只会在最开始的时候执行一次或多次,所以对整个系统的性能不会有影响。
2.2 单例对象的属性更新同步
为了解决第2个问题,有两种方法:
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属性中。
上面两个方法比较起来,第二个方法更好,首先,编程更简单;其次,没有那么多的同步操作,对性能的影响也不大。
本文将探讨一下在多线程环境下,使用单例对象作配置信息管理时可能会带来的几个同步问题,并针对每个问题给出可选的解决办法。
问题描述
在多线程环境下,单例对象的同步问题主要体现在两个方面,单例对象的初始化和单例对象的属性更新。
本文描述的方法有如下假设:
1. 单例对象的属性(或成员变量)的获取,是通过单例对象的初始化实现的。也就是说,在单例对象初始化时,会从文件或数据库中读取最新的配置信息。
2. 其他对象不能直接改变单例对象的属性,单例对象属性的变化来源于配置文件或配置数据库数据的变化。
1.1 单例对象的初始化
首先,讨论一下单例对象的初始化同步。单例模式的通常处理方式是,在对象中有一个静态成员变量,其类型就是单例类型本身;如果该变量为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中有多个单例类型的实例。如果这个单例类型的成员变量在运行过程中变化,会造成多个单例类型实例的不一致,产生一些很奇怪的现象。例如,某服务进程通过检查单例对象的某个属性来停止多个线程服务,如果存在多个单例对象的实例,就会造成部分线程服务停止,部分线程服务不能停止的情况。
1.2 单例对象的属性更新
通常,为了实现配置信息的实时更新,会有一个线程不停检测配置文件或配置数据库的内容,一旦发现变化,就更新到单例对象的属性中。在更新这些信息的时候,很可能还会有其他线程正在读取这些信息,造成意想不到的后果。还是以通过单例对象属性停止线程服务为例,如果更新属性时读写不同步,可能访问该属性时这个属性正好为空(null),程序就会抛出异常。
解决方法
2.1 单例对象的初始化同步
对于初始化的同步,可以通过如下代码所采用的方式解决。
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;
}
}
这种处理方式虽然引入了同步代码,但是因为这段同步代码只会在最开始的时候执行一次或多次,所以对整个系统的性能不会有影响。
2.2 单例对象的属性更新同步
为了解决第2个问题,有两种方法:
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属性中。
上面两个方法比较起来,第二个方法更好,首先,编程更简单;其次,没有那么多的同步操作,对性能的影响也不大。
2009/07/23 16:10
一台服务器 几乎所有网站打开网页 甚至HTML网页 都出现了
这种样式的代码 有的在头部 有的在尾部 部分杀毒软件打开会报毒
打开HTML或ASP PHP页面 在源码中怎么也找不到这段代码
分析原因
首先怀疑ARP挂马,用防ARP的工具又没有发现有arp欺骗
而且arp欺骗一般不会每次都被插入代码,而是时有时无
而且使用http://127.0.0.1 或者http://localhost 访问的时候也可以找到这段代码
arp欺骗的可能排除。
然后就想到可能是JS被篡改,或者是其它的包含文件,查找后没有发现被改的页面 连新建的HTML页面浏览的时候也会被插入这段代码,那就只能是通过IIS挂上去的了。
备份iis数据然后重装iis,代码消失,将备份的iis恢复,问题又来了。
仔细寻找,问题应该出在IIS的配置文件上,打开配置文件,没有发现那段代码。
那很有可能是调用了某个文件,这个怎么查啊,忽然想起了大名鼎鼎的Filemon
本地载了一个上传到服务器上,打开Filemon,数据太多了,过滤掉一些没有用的
只留下iis的进程,数据还是很多,看来服务器上的站点还是挺多人在访问的。
关掉所有站点,建了一个测试站点anky 目录为D:\www\ 在下面建了一个空白页面test.htm
访问一下这个页面代码被插进来了,再看一下Filemon 奇怪怎么读取C:\Inetpub\wwwroot\iisstart.htm
打开C:\Inetpub\wwwroot\iisstart.htm一看,里面就躺着
把代码删除了留空,访问test.htm 正常了,把C:\Inetpub\wwwroot\iisstart.htm删除了再访问
test.htm 出现 “读取数据页脚文件出错”问题就出这里了,看来是调用了
这个文件。
把C:\Inetpub\wwwroot\iisstart.htm清空就正常了,这样怎么行,解决问题当然要连根拔掉。
continue
有没有可能是扩展造成的,到扩展中检查了一遍全部都是正常的
当然 通过ISAPI 挂马的也是存在的
左想右想最后还是觉得配置文件有问题
打开配置文件,配置文件在%windir%\system32\inetsrv\MetaBase.xml
用记事本打开,查找iisstart.htm 找到一行,开始以为是默认站点,后来一想不对啊
默认站点都删除了,再仔细一看这句代码为
DefaultDocFooter="FILE:C:\Inetpub\wwwroot\iisstart.htm"
删除掉这一行,问题彻底解决了
这种样式的代码 有的在头部 有的在尾部 部分杀毒软件打开会报毒
打开HTML或ASP PHP页面 在源码中怎么也找不到这段代码
分析原因
首先怀疑ARP挂马,用防ARP的工具又没有发现有arp欺骗
而且arp欺骗一般不会每次都被插入代码,而是时有时无
而且使用http://127.0.0.1 或者http://localhost 访问的时候也可以找到这段代码
arp欺骗的可能排除。
然后就想到可能是JS被篡改,或者是其它的包含文件,查找后没有发现被改的页面 连新建的HTML页面浏览的时候也会被插入这段代码,那就只能是通过IIS挂上去的了。
备份iis数据然后重装iis,代码消失,将备份的iis恢复,问题又来了。
仔细寻找,问题应该出在IIS的配置文件上,打开配置文件,没有发现那段代码。
那很有可能是调用了某个文件,这个怎么查啊,忽然想起了大名鼎鼎的Filemon
本地载了一个上传到服务器上,打开Filemon,数据太多了,过滤掉一些没有用的
只留下iis的进程,数据还是很多,看来服务器上的站点还是挺多人在访问的。
关掉所有站点,建了一个测试站点anky 目录为D:\www\ 在下面建了一个空白页面test.htm
访问一下这个页面代码被插进来了,再看一下Filemon 奇怪怎么读取C:\Inetpub\wwwroot\iisstart.htm
打开C:\Inetpub\wwwroot\iisstart.htm一看,里面就躺着
把代码删除了留空,访问test.htm 正常了,把C:\Inetpub\wwwroot\iisstart.htm删除了再访问
test.htm 出现 “读取数据页脚文件出错”问题就出这里了,看来是调用了
这个文件。
把C:\Inetpub\wwwroot\iisstart.htm清空就正常了,这样怎么行,解决问题当然要连根拔掉。
continue
有没有可能是扩展造成的,到扩展中检查了一遍全部都是正常的
当然 通过ISAPI 挂马的也是存在的
左想右想最后还是觉得配置文件有问题
打开配置文件,配置文件在%windir%\system32\inetsrv\MetaBase.xml
用记事本打开,查找iisstart.htm 找到一行,开始以为是默认站点,后来一想不对啊
默认站点都删除了,再仔细一看这句代码为
DefaultDocFooter="FILE:C:\Inetpub\wwwroot\iisstart.htm"
删除掉这一行,问题彻底解决了
2009/07/23 16:09
91D2盛大开荒...征文活动成功举办...
离不开一直以来默默支持我们的朋友...谢谢大家..
这一次征文虽然说不上是百花齐放,也算得上异彩纷呈,着实让评委们伤透了脑筋。
取舍之间,左右为难。
然而,失落与幸运是孪生兄弟。
如同我们在游戏在生活中mf一般,没有失意,哪有惊喜。
在此,先向没有获奖的朋友们说一声:感谢你们的参与,91D2因为你们而感动!
附上活动奖励与所有作品的链接:
【活动奖励】
第一名:20ist+bbs5000ist勋章一枚(名字SC来定)好评度 10威望3 (一名)
第二名:12ist+bbs2000ist好评度 5威望2(二名)
第三名:6ist+bbs1000ist 好评度2 威望1(三名)
优胜奖:2ist+bbs500ist (八名)
最佳评委奖(一名):2ist
最佳点评奖(一名):2ist
竞猜幸运奖(一名):2ist
作品链接:
http://bbs.91d2.cn/read-ht...
关于奖品发放:
1、获奖的朋友可以于每日19:30--22:00论坛PM我或者有我QQ的可以QQ联系我(对不住大家了,平时上班上不了游戏,晚上到家要到19:30了,给您造成的不便,敬请谅解) ;
2、告知交易的国度,房间 (要麻烦大家建n的时候用普通房间),并在领取完奖品后跟帖确认;
例: 媳妇 确认已经领奖
3、如果获奖的朋友上游戏的时间跟我有冲突,也可以PM我空白的ACC,我会把奖品发放到您的账号里,也请发放完毕后查收确认;
内个..bbsist、威望、好评、勋章,请等待管理层发放
离不开一直以来默默支持我们的朋友...谢谢大家..
这一次征文虽然说不上是百花齐放,也算得上异彩纷呈,着实让评委们伤透了脑筋。
取舍之间,左右为难。
然而,失落与幸运是孪生兄弟。
如同我们在游戏在生活中mf一般,没有失意,哪有惊喜。
在此,先向没有获奖的朋友们说一声:感谢你们的参与,91D2因为你们而感动!
附上活动奖励与所有作品的链接:
【活动奖励】
第一名:20ist+bbs5000ist勋章一枚(名字SC来定)好评度 10威望3 (一名)
第二名:12ist+bbs2000ist好评度 5威望2(二名)
第三名:6ist+bbs1000ist 好评度2 威望1(三名)
优胜奖:2ist+bbs500ist (八名)
最佳评委奖(一名):2ist
最佳点评奖(一名):2ist
竞猜幸运奖(一名):2ist
作品链接:
http://bbs.91d2.cn/read-ht...
关于奖品发放:
1、获奖的朋友可以于每日19:30--22:00论坛PM我或者有我QQ的可以QQ联系我(对不住大家了,平时上班上不了游戏,晚上到家要到19:30了,给您造成的不便,敬请谅解) ;
2、告知交易的国度,房间 (要麻烦大家建n的时候用普通房间),并在领取完奖品后跟帖确认;
例: 媳妇 确认已经领奖
3、如果获奖的朋友上游戏的时间跟我有冲突,也可以PM我空白的ACC,我会把奖品发放到您的账号里,也请发放完毕后查收确认;
内个..bbsist、威望、好评、勋章,请等待管理层发放


