Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java: How to atomically replace all values in a Map?

Tags:

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.

like image 789
Jan Avatar asked May 14 '18 13:05

Jan


2 Answers

Approach with CAS and AtomicReference would be to copy map content on each bulk update.

AtomicReference<Map<String, String>> workingMapRef = new AtomicReference<>(new HashMap<>());

This map can be concurrent, but for "bulk updates" it is read-only. Then in updateState looping doUpdateState() until you get true and that means that your values has been updated.

void updateState() {
    while (!doUpdateState());
}

boolean doUpdateState() {
    Map<String, String> workingMap = workingMapRef.get();
    //copy map content
    Map<String, String> newState = new HashMap<>(workingMap); //you can make it concurrent

    newState.put("b", "b1");
    newState.put("c", "c2");
    newState.put("d", "d1");

    return workingMapRef.compareAndSet(workingMap, newState);
}
like image 188
schaffe Avatar answered Oct 24 '22 22:10

schaffe


The simplest, least fuss method is to switch the map instead of replacing map contents. Whether using volatile or AtomicReference (I don't see why you'd need AtomicReferenceFieldUpdater 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 you CAS 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 over ConcurrentHashMap.

like image 22
Kayaman Avatar answered Oct 24 '22 21:10

Kayaman