我正在努力使我的类的线程安全实现可以在Servlet或代码中的多个“客户端”之间共享。
假设我有以下MySingleton
类,它是用Configuration
对象初始化的单例。但是,可以观察到Configuration
对象,因此如果进行了更改,则单例将订阅通知。重要要点:
该配置可以随时更改(无法预测)
解析配置后,其值将保存到MySingleton的成员字段中
单例的public方法使用这些字段来生成返回结果
请参见下面的简化代码:
public class MySingleton
implements IConfigurationObserver {
// Member(s)
private static volatile MySingleton instance;
private final Configuration configuration;
private String firstParam;
private String secondParam;
// Constructor(s)
private MySingleton(Configuration configuration) {
this.configuration = configuration;
parseConfiguration();
configuration.addObserver(this);
}
public static MySingleton getInstance(Configuration configuration) {
// Perform synchronized creation if applicable (double locking technique)
MySingleton syncInstance = instance;
if (syncInstance == null) {
synchronized(MySingleton.class) {
syncInstance = instance; // Verify once again after synchronizing
if(syncInstance == null) {
syncInstance = new MySingleton(configuration);
instance = syncInstance;
}
}
}
return syncInstance;
}
// Private Method(s)
private void parseConfiguration() {
// Import values from the configuration
this.firstParam = configuration.get(0);
this.secondParam = configuration.get(1);
}
// Public Method(s)
public String buildSentence() {
// Build a new string based on values pulled from the configuration
StringBuilder strBuilder = new StringBuilder();
strBuilder.append(firstParam);
strBuilder.append(" - ");
strBuilder.append(secondParam);
return strBuilder.toString();
}
// Observer Method(s)
@Override
public void onConfigurationUpdated() {
// The configuration has changed. Parse it again.
parseConfiguration();
}
}
该类自己可以正常工作(在单线程环境中),但是在多线程场景中,我想消除一些威胁:
如果在很短的时间内对
Configuration
进行了两次更新,则可能在第二个调用开始之前没有完成对parseConfiguration()
的第一次调用。这很容易解决,我可以使parseConfiguration()
同步(对吗?)。但...假设当配置通知我们单身人士时,我们正处于对
buildSentence()
的调用中。我不希望buildSentence()
为firstParam和secondParam使用旧值的混合(即,如果parseConfiguration()
已完成一半)。这样,我可以在Configuration
和parseConfiguration()
的buildSentence()
对象上放置一个同步块,但是这样会严重影响性能:对buildSentence()
的并发调用不能超过一个。实际上,对我来说理想的情况是:如果
buildSentence()
正在运行并且发生Configuration
更新,则parseConfiguration()
必须等到buildSentence()
结束后再运行如果
parseConfiguration()
正在运行,则对buildSentence()
的调用必须等到parseConfiguration()
结束后才能开始但是,一旦
parseConfiguration()
完成,我想允许多个线程同时运行buildSentence()
。只有在即将进行更新或正在进行更新时,才应进行锁定。我如何重构MySingleton以允许上面列出的理想“规则”?可能吗?
我一直在想一个涉及信号量的解决方案。即:执行
buildSentence()
时,请检查信号灯是否可用。如果是,请继续(无阻塞)。如果否,请等待。并且parseConfiguration
将在信号执行期间锁定信号量。但是,如果有人有直接的建议方法,我不想过度设计这个问题。让我知道! 最佳答案
我想我会喜欢这样的东西:
public class MySingleton
implements IConfigurationObserver {
// Member(s)
private static volatile MySingleton instance;
private final Configuration configuration;
private volatile ParsedConfiguration currentConfig;
// Constructor(s)
private MySingleton(Configuration configuration) {
this.configuration = configuration;
parseConfiguration();
configuration.addObserver(this);
}
public static MySingleton getInstance(Configuration configuration) {
// Perform synchronized creation if applicable (double locking technique)
MySingleton syncInstance = instance;
if (syncInstance == null) {
synchronized(MySingleton.class) {
syncInstance = instance; // Verify once again after synchronizing
if(syncInstance == null) {
syncInstance = new MySingleton(configuration);
instance = syncInstance;
}
}
}
return syncInstance;
}
// Private Method(s)
private void parseConfiguration() {
// Import values from the configuration
currentConfig = configuration.getNewParsedConfiguration();
}
// Public Method(s)
public String buildSentence() {
// Build a new string based on values pulled from the current configuration
ParsedConfiguration configInUse = currentConfig;
StringBuilder strBuilder = new StringBuilder();
strBuilder.append(configInUse.getFirstParam());
strBuilder.append(" - ");
strBuilder.append(configInUse.getSecondParam());
return strBuilder.toString();
}
// Observer Method(s)
@Override
public void onConfigurationUpdated() {
// The configuration has changed. Parse it again.
parseConfiguration();
}
}
请注意将
currentConfig
移到buildSentence
开头的局部变量的安全措施,这样对使用的ParsedConfig
的引用就不能更改中间方法。