我正在努力绕过我的类的线程安全实现,以便在Servlet或代码中的多个“客户”之间共享。
假设我有以下MySingleton
类,一个用Configuration
对象初始化的单例。但是,可以观察到Configuration
对象,因此如果进行了更改,单身人员将订阅通知。重要的关键点:
配置可随时更改(无法预测)
解析配置后,其值将保存到MySingleton的成员字段中
单身人士的公共方法使用这些字段生成返回结果
请参阅以下简化代码:
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()
的{{1}}对象上放置一个同步块,但后来我遇到了严重的性能损失:我不能有多个并发调用{ {1}}。事实上,对我来说理想的情况是:
如果buildSentence()
正在运行且发生buildSentence()
更新,则buildSentence()
必须等到Configuration
结束才能运行
如果parseConfiguration()
正在投放,则对buildSentence()
的调用必须等到parseConfiguration()
结束才能开始
但是,一旦buildSentence()
完成,我想允许多个线程同时运行parseConfiguration()
。只有在即将发生或正在进行更新时才会发生锁定。
如何重构MySingleton以允许我在上面列出的理想“规则”?有可能吗?
我一直在想一个涉及信号量的解决方案。即:执行parseConfiguration()
时,检查信号量是否可用。如果是,请继续(不阻止)。如果不是,请等并且buildSentence()
会在执行期间锁定信号量。但是,如果某人有一个直截了当的建议方法,我不想过度设计这个问题。让我知道!
答案 0 :(得分:3)
我想我会选择这样的事情:
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
的引用无法更改中间方法。< / p>