If multiple threads are going to share an instance, you must use some kind of synchronization.
Synchronization is needed mainly for two reasons:
- It can guarantee that some operations are atomic, so the system will keep consistent
- It guarantees that every threads sees the same values in the memory
First of all, since you made reloadConfig()
public, your object does not really look immutable. If the object is really immutable, that is, if after initialization of its values they cannot change (which is a desired property to have in objects that are shared).
For the above reason, you must synchronize all the access to the map: suppose a thread is trying to read from it while another thread is calling reloadConfig()
. Bad things will happen.
If this is really the case (mutable settings), you must synchronize in both reads and writes (for obvious reasons). Threads must synchronize on a single object (otherwise there's no synchronization). The only way to guarantee that all the threads will synchronize on the same object is to synchronize on the object itself or in a properly published, shared, lock, like this:
// synchronizes on the in instance itself:
class MyDBConfig1 {
// ...
public synchronized String getProperty(...) { ... }
public synchronized reloadConfig() { ... }
}
// synchronizes on a properly published, shared lock:
class MyDBConfig2 {
private final Object lock = new Object();
public String getProperty(...) { synchronized(lock) { ... } }
public reloadConfig() { synchronized(lock) { ... } }
}
The properly publication here is guaranteed by the final keyword. It is subtle: it guarantees that the value of this field is visible to every thread after initialization (without it, a thread might see that lock == null
, and bad things will happen).
You could improve the code above by using a (properly published) ReadWriteReentrantLock
. It might improve concurrency a bit if that's a concern for you.
Supposing your intention was to make MyDBConfig
immutable, you do not need to serialize access to the hash map (that is, you don't necessarily need to add the synchronized keyword). You might improve concurrency.
First of all, make reloadConfig()
private (this will indicate that, for consumers of this object, it is indeed immutable: the only method they see is getProperty(...)
, which, by its name, should not modify the instance).
Then, you only need to guarantee that every thread will see the correct values in the hash map. To do so, you could use the same techniques presented above, or you could use a volatile
field, like this:
class MyDBConfig {
private volatile boolean initialized = false;
public String getProperty(...) { if (initialized) { ... } else { throw ... } }
private void reloadConfig() { ...; initialized = true; }
public MyDBConfig(...) { ...; reloadConfig(); }
}
The volatile
keyword is very subtle. Volatile writes and volatile reads have a happens-before relationship. A volatile write is said to happen-before a subsequent volatile read of the same (volatile) field. What this means is that all the memory locations that have been modified before (in program order) a volatile write are visible to every other thread after they have executed a subsequente volatile read of the same (volatile) field.
In the code above, you write true
to the volatile field after all the values have been set. Then, the method reading values (getProperty(...)
) begins by executing a volatile read of the same field. Then this method is guaranteed to see the correct values.
In the example above, if you don't publish the instance before the constructor finishes, it is guaranteed that the exception won't get thrown in the method getProperty(...)
(because before the constructor finishes, you have written true
to initialized).