2

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 to parseConfiguration() is not finished before the second call begins. This one is easy to solve, I could just make parseConfiguration() synchronized (right?). But...

  • Pretend that we are in the middle of a call to buildSentence() when the Configuration notifies our singleton. I don't want buildSentence() to use a mix of old values for firstParam and secondParam (i.e. if parseConfiguration() is halfway done). 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(). 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

    • If parseConfiguration() is running, a call to buildSentence() must wait until parseConfiguration() is over before starting

    • However, once parseConfiguration() is finished, I want to allow multiple threads to run buildSentence() 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!

Duncan Jones
  • 67,400
  • 29
  • 193
  • 254
hbCyber
  • 773
  • 8
  • 17
  • a good [resource](http://stackoverflow.com/questions/70689/what-is-an-efficient-way-to-implement-a-singleton-pattern-in-java/71399#71399) for singletons in java. – Olimpiu POP Aug 29 '14 at 06:33
  • FYI - I don't think the fact that this class is a singleton will have any impact on the correct answer to this question. – Duncan Jones Aug 29 '14 at 06:37
  • @Duncan You're correct. I might rephrase the question to make it more general. – hbCyber Aug 29 '14 at 06:39
  • 1
    I think good strategy might be to split Configuration into two classes - one is Configuration itself as immutable value class which can be passed to observers and observer is responsible to swap it in atomic way, the second class is ConfigurationHolder, which holds current Configuration value and is responsible for notifying observers. With this design parseConfiguration doesn't have the problem with Configuration changing in the middle of the process and buildSentence is always using current consistent value of Configuration. Do you think this might work? – Tibor Blenessy Aug 29 '14 at 06:54
  • 1
    There is no way of sharing objects between threads without a performance penalty. The penalty can be made small. – Raedwald Aug 29 '14 at 07:03
  • @Raedwald I should point out that I renamed the question, so the error of asking for no performance hit is mine. (But it was simpler to read than a longer title asking for minimal impact to performance). – Duncan Jones Aug 29 '14 at 07:12
  • @Duncan There is still the penalty of a volatile write and read. – Marko Topolnik Aug 29 '14 at 07:13
  • @MarkoTopolnik I am using JDK 1.8 and a volatile reference. What is broken in my double-checked locking implementation? – hbCyber Aug 29 '14 at 07:16
  • My comment has already been withdrawn after I have noted your `volatile` modifier on the singleton reference. Secondly, if you want better performance, you should use the static holder class idiom. Not that the performance will get detectably better. – Marko Topolnik Aug 29 '14 at 07:17
  • Looks to me that you want a [ReadWriteLock](http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/ReadWriteLock.html) – kajacx Aug 29 '14 at 07:27
  • 1
    "*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*" => have you measured that? Unless there is a high contention and you call `parseConfiguration` millions of times per second, the performance hit would probably be a lot less than you seem to think. – assylias Aug 29 '14 at 07:31
  • 1
    @assylias +1 with a note that OP would be calling `buildSentence` millions of times per second. – Marko Topolnik Aug 29 '14 at 07:38

1 Answers1

3

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.

piet.t
  • 11,718
  • 21
  • 43
  • 52
  • I'm not sure `getNewParsedConfiguration()` needs to be `synchronized` - can you explain why? Note to OP: `ParsedConfiguration` really does need to be immutable (not just effectively immutable) for this to work. – Duncan Jones Aug 29 '14 at 07:11
  • 1
    @Duncan Not only does it not *have to* be synchronized, it doesn't help thread safety if it is. Instead, `currentConfig` needs to be `volatile`. – Marko Topolnik Aug 29 '14 at 07:16
  • @MarkoTopolnik Agreed, without `volatile` some threads may see old values for `currentConfig`. – Duncan Jones Aug 29 '14 at 07:20
  • 1
    @Duncan Plus, with volatile you don't need more than an *effectively immutable* object. – Marko Topolnik Aug 29 '14 at 07:20
  • @Duncan: thinking about it again you should be right - `getNewParsedConfiguration` doesn't necessarily have to be `synchronized` since two invocations just return two new objects. There might be implementation issues that might require this but then again there might not. I'll remove that part. – piet.t Aug 29 '14 at 07:21
  • @Duncan and I'll add the `volatile` - thanks for the corrections. – piet.t Aug 29 '14 at 07:22
  • I think @MarkoTopolnik needs the thanks here. Excellent comments from him. – Duncan Jones Aug 29 '14 at 07:24