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.
Pretend I have the following MySingleton
class, a singleton that gets initialized with a Configuration
object. However, the Configuration
object can be observed, and so the singleton will subscribe to be notified if changes are made. 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
The singleton's public method uses those fields to generate a return result
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 toparseConfiguration()
is not finished before the second call begins. This one is easy to solve, I could just makeparseConfiguration()
synchronized (right?). But...Pretend that we are in the middle of a call to
buildSentence()
when the Configuration notifies our singleton. I don't wantbuildSentence()
to use a mix of old values for firstParam and secondParam (i.e. ifparseConfiguration()
is halfway done). As such, I could put a synchronized block on theConfiguration
object for bothparseConfiguration()
andbuildSentence()
, but then I have a severe performance hit: I cannot have more than one concurrent call tobuildSentence()
. In truth, the ideal situation for me is that:If
buildSentence()
is running and aConfiguration
update occurs,parseConfiguration()
has to wait untilbuildSentence()
is over before runningIf
parseConfiguration()
is running, a call tobuildSentence()
must wait untilparseConfiguration()
is over before startingHowever, once
parseConfiguration()
is finished, I want to allow multiple threads to runbuildSentence()
at the same time. 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? Is it possible?
I have been thinking of a solution which involves Semaphores. i.e.: when performing buildSentence()
, check if the semaphore is available. If yes, go ahead (no blocking). If no, wait. And parseConfiguration
would lock the semaphore during its execution. However I don't want to over-engineer this problem if someone has a straightforward approach to suggest. Let me know!