I have a stateful bean in an multi-threaded enviroment, which keeps its state in a map. Now I need a way to replace all values of that map in one atomic action.
public final class StatefulBean {
private final Map<String, String> state = new ConcurrentSkipListMap<>();
public StatefulBean() {
//Initial state
this.state.put("a", "a1");
this.state.put("b", "b1");
this.state.put("c", "c1");
}
public void updateState() {
//Fake computation of new state
final Map<String, String> newState = new HashMap<>();
newState.put("b", "b1");
newState.put("c", "c2");
newState.put("d", "d1");
atomicallyUpdateState(newState);
/*Expected result
* a: removed
* b: unchanged
* C: replaced
* d: added*/
}
private void atomicallyUpdateState(final Map<String, String> newState) {
//???
}
}
At the moment I use ConcurrentSkipListMap
as implementation of a ConcurrentMap
, but that isn't a requirement.
The only way I see to solve this problem is to make the global state
volatile
and completely replace the map or use a AtomicReferenceFieldUpdater
.
Is there a better way?
My updates are quite frequent, once or twice a second, but chance only very few values. Also the whole map will only ever contain fewer than 20 values.
Approach with CAS and
AtomicReference
would be to copy map content on each bulk update.This map can be concurrent, but for "bulk updates" it is read-only. Then in
updateState
loopingdoUpdateState()
until you get true and that means that your values has been updated.As client code maintains a reference to the bean not the map, replacing the value (i.e. the whole map) would seem to be the simplest solution.
Unless there's any significant performance concerns (although using locking is likely to perform worse and less predictably unless the map is huge) I'd try that before anything requiring more advanced knowledge.
It's how a functional programmer would do it.
The simplest, least fuss method is to switch the map instead of replacing map contents. Whether using
volatile
orAtomicReference
(I don't see why you'd needAtomicReferenceFieldUpdater
particularly), shouldn't make too much of a difference.This makes sure that your map is always in proper state, and allows you to provide snapshots too. It doesn't protect you from other concurrency issues though, so if something like lost updates are a problem you'll need further code (although
AtomicReference
would give youCAS
methods for handling those).The question is actually rather simple if you only consider the complete atomic replacement of the map. It would be informative to know what other operations affect the map and how. I'd also like to hear why
ConcurrentSkipListMap
was chosen overConcurrentHashMap
.Since the map is quite small, it's probably enough to just use
synchronized
in all places you access it.but don't forget any, like all occurances of things like
need to become
otherwise the read and update are not synchronized and cause a race condition.
Extend a map implementation of your choice and add a synchronized method:
Of course, you could always make
state
non-final volatile and re-assign it (assignment is atomic)