[英]Thread-safe update of shared object without performance penalty?
I'm struggling to wrap my head around making a thread-safe implementation of my class to be shared amongst Servlets or multiple 'clients' in the code. 我正在努力使我的类的线程安全实现可以在Servlet或代码中的多个“客户端”之间共享。
Pretend I have the following MySingleton
class, a singleton that gets initialized with a Configuration
object. 假设我有以下
MySingleton
类,该类是使用Configuration
对象初始化的。 However, the Configuration
object can be observed, and so the singleton will subscribe to be notified if changes are made. 但是,可以观察到
Configuration
对象,因此如果进行更改,单例将订阅通知。 Important key points: 重要要点:
The configuration can change at any time (impossible to predict) 该配置可以随时更改(无法预测)
When the configuration is parsed, its values are saved into member fields of MySingleton 解析配置后,其值将保存到MySingleton的成员字段中
The singleton's public method uses those fields to generate a return result 单例的public方法使用这些字段来生成返回结果
See simplified code below: 请参见下面的简化代码:
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();
}
}
The class works fine on its own (in a single thread environment), but here's a few threats I want to eliminate in multithreaded scenarios: 该类自己可以正常工作(在单线程环境中),但是在多线程场景中,我想消除一些威胁:
If two updates are made to the Configuration
in a very short time, it's possible that the first call to parseConfiguration()
is not finished before the second call begins. 如果在很短的时间内对
Configuration
进行了两次更新,则可能在第二次调用开始之前没有完成对parseConfiguration()
的第一次调用。 This one is easy to solve, I could just make parseConfiguration()
synchronized (right?). 这很容易解决,我可以使
parseConfiguration()
同步(对吗?)。 But... 但...
Pretend that we are in the middle of a call to buildSentence()
when the Configuration notifies our singleton. 假设当Configuration通知我们的单身人士时,我们正处于对
buildSentence()
的调用中间。 I don't want buildSentence()
to use a mix of old values for firstParam and secondParam (ie if parseConfiguration()
is halfway done). 我不希望
buildSentence()
为firstParam和secondParam使用旧值的混合(即,如果parseConfiguration()
已完成一半)。 As such, I could put a synchronized block on the Configuration
object for both parseConfiguration()
and buildSentence()
, but then I have a severe performance hit: I cannot have more than one concurrent call to buildSentence()
. 这样,我可以在
parseConfiguration()
和buildSentence()
的Configuration
对象上放置一个同步块,但是这样会严重打击性能:我不能对buildSentence()
进行多个并发调用。 In truth, the ideal situation for me is that: 实际上,对我来说理想的情况是:
If buildSentence()
is running and a Configuration
update occurs, parseConfiguration()
has to wait until buildSentence()
is over before running 如果
buildSentence()
正在运行并且发生Configuration
更新,则parseConfiguration()
必须等到buildSentence()
结束后再运行
If parseConfiguration()
is running, a call to buildSentence()
must wait until parseConfiguration()
is over before starting 如果
parseConfiguration()
正在运行,则对buildSentence()
的调用必须等到parseConfiguration()
结束后才能开始
However, once parseConfiguration()
is finished, I want to allow multiple threads to run buildSentence()
at the same time. 但是,一旦
parseConfiguration()
完成,我想允许多个线程同时运行buildSentence()
。 Locking should only occur if an update is about to take place, or taking place. 只有在即将进行更新或正在进行更新时,才应进行锁定。
How can I refactor MySingleton to allow for the ideal 'rules' I've listed above? 我如何重构MySingleton以允许上面列出的理想“规则”? Is it possible?
可能吗?
I have been thinking of a solution which involves Semaphores. 我一直在想一个涉及信号量的解决方案。 ie: when performing
buildSentence()
, check if the semaphore is available. 即:在执行
buildSentence()
,检查信号量是否可用。 If yes, go ahead (no blocking). 如果是,请继续(无阻塞)。 If no, wait.
如果否,请等待。 And
parseConfiguration
would lock the semaphore during its execution. 并且
parseConfiguration
将在信号执行期间锁定该信号。 However I don't want to over-engineer this problem if someone has a straightforward approach to suggest. 但是,如果有人有直接的建议方法,我不想过度设计这个问题。 Let me know!
让我知道!
I think I would go for something like this: 我想我会喜欢这样的东西:
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();
}
}
Take note of the safeguard to move currentConfig
to a local variable at the beginning of buildSentence
so the reference to the used ParsedConfig
can't change mid-method. 请注意在
buildSentence
开头将currentConfig
移至局部变量的buildSentence
,以确保对使用的ParsedConfig
的引用不能更改中间方法。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.