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

后端 未结 6 1272
忘了有多久
忘了有多久 2020-12-11 07:04

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.

         


        
相关标签:
6条回答
  • 2020-12-11 07:14

    Since the map is quite small, it's probably enough to just use synchronized in all places you access it.

    private void atomicallyUpdateState(final Map<String, String> newState) {
        synchronized(state) {
            state.clear();
            state.putAll(newState);
        }
    }
    

    but don't forget any, like all occurances of things like

    String myStatevalue = state.get("myValue");
    

    need to become

    String myStatevalue;
    synchronized (state) {
        myStatevalue = state.get("myValue");
    }
    

    otherwise the read and update are not synchronized and cause a race condition.

    0 讨论(0)
  • 2020-12-11 07:25

    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);
    }
    
    0 讨论(0)
  • 2020-12-11 07:25

    Extend a map implementation of your choice and add a synchronized method:

    class MyReplaceMap<K, V> extends HashMap<K, V> //or whatever
    {
        public synchronized void replaceKeys(final Map<K, V> newMap)
        {
            //.. do some stuff
        }
    }
    

    Of course, you could always make state non-final volatile and re-assign it (assignment is atomic)

    private volatile Map<String, String> state = new HashMap<>();
    
    //...
    
    final Map<String, String> newState = new HashMap<>();
    newState.put("b", "b1");
    newState.put("c", "c2");
    newState.put("d", "d1");
    state = newState;
    
    0 讨论(0)
  • 2020-12-11 07:29

    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.

    0 讨论(0)
  • 2020-12-11 07:29

    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.

    0 讨论(0)
  • 2020-12-11 07:34

    Use ReadWriteLock can help to automically replace all values in a Map.

    private static final ReadWriteLock LOCK = new ReentrantReadWriteLock();
    
    private void atomicallyUpdateState(final Map<String, String> newState) {
        LOCK.writeLock().lock();
        try {
            state.clear();
            state.putAll(newState);
        } finally {
            LOCK.writeLock().unlock();
        }
    }
    
    0 讨论(0)
提交回复
热议问题