【发布时间】:2014-08-29 06:29:51
【问题描述】:
我正在努力解决我的类的线程安全实现,以便在 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()完成一半)。因此,我可以为parseConfiguration()和buildSentence()在Configuration对象上放置一个同步块,但是我的性能受到严重影响:我不能同时调用多个buildSentence()。事实上,对我来说理想的情况是:如果
buildSentence()正在运行并且发生Configuration更新,则parseConfiguration()必须等到buildSentence()结束才能运行如果
parseConfiguration()正在运行,则对buildSentence()的调用必须等到parseConfiguration()结束后才能开始但是,一旦
parseConfiguration()完成,我想允许多个线程同时运行buildSentence()。仅当更新即将发生或正在发生时才应进行锁定。
如何重构 MySingleton 以支持我上面列出的理想“规则”?有可能吗?
我一直在考虑一个涉及信号量的解决方案。即:执行buildSentence()时,检查信号量是否可用。如果是,请继续(不阻塞)。如果没有,请等待。而parseConfiguration 将在信号量执行期间锁定信号量。但是,如果有人有直接的建议方法,我不想过度设计这个问题。告诉我!
【问题讨论】:
-
一个很好的 resource 用于 java 中的单例。
-
我认为好的策略可能是将 Configuration 拆分为两个类 - 一个是 Configuration 本身作为不可变值类,可以传递给观察者,观察者负责以原子方式交换它,第二个类是ConfigurationHolder,保存当前的Configuration值,负责通知观察者。通过这种设计,parseConfiguration 不会在过程中间更改配置的问题,并且 buildSentence 始终使用当前一致的配置值。你认为这可行吗?
-
没有性能损失的线程之间共享对象的方法。处罚可以很小。
-
"因此,我可以在 parseConfiguration() 和 buildSentence() 的配置对象上放置一个同步块,但随后我的性能受到严重影响" =>你测量过吗?除非存在高争用并且您每秒调用
parseConfiguration数百万次,否则性能损失可能比您想象的要少得多。 -
@assylias +1 并注明 OP 每秒会调用
buildSentence数百万次。
标签: java multithreading semaphore synchronized