代码之家  ›  专栏  ›  技术社区  ›  Jeremy

如何以原子方式替换单例映射的所有值?[副本]

  •  1
  • Jeremy  · 技术社区  · 8 年前

    我在多线程环境中有一个状态bean,它将其状态保存在映射中。现在我需要一种方法在一个原子操作中替换该映射的所有值。

    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) {
            //???
        }
    }
    

    ConcurrentSkipListMap 作为 ConcurrentMap ,但这不是一个要求。

    state volatile 并完全替换地图或使用 AtomicReferenceFieldUpdater . 有更好的办法吗?

    我的更新相当频繁,每秒一到两次,但机会值很少。此外,整个地图将只包含少于20个值。

    0 回复  |  直到 6 年前
        1
  •  2
  •   Kayaman    8 年前

    最简单、最轻松的方法是切换地图,而不是替换地图内容。是否使用 volatile AtomicReference (我不明白你为什么需要 AtomicReferenceFieldUpdater 特别是),不应该有太大的区别。

    这样可以确保地图始终处于正确的状态,并允许您提供快照。但它不能保护您免受其他并发性问题的影响,因此如果出现诸如丢失更新之类的问题,您将需要进一步的代码(尽管 原子参考 我会给你 CAS 处理这些问题的方法)。

    ConcurrentSkipListMap ConcurrentHashMap .

        2
  •  2
  •   Basil Bourque    6 年前

    与CAS和 AtomicReference 将是在每次批量更新时复制地图内容。

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

    此映射可以是并发的,但对于“批量更新”,它是只读的。然后在 updateState 循环 doUpdateState()

    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);
    }
    
        3
  •  0
  •   daniu    8 年前

    因为这张地图很小,可能就足够用了 synchronized 在所有你可以访问的地方。

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

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

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

    否则,读取和更新将不同步,并导致竞争条件。

        4
  •  0
  •   Michael    8 年前

    扩展您选择的映射实现并添加同步方法:

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

    当然,你总是可以 state 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;
    
        5
  •  0
  •   brabster    8 年前

    由于客户机代码维护对bean的引用而不是映射,因此替换值(即整个映射)似乎是最简单的解决方案。

    除非有任何显著的性能问题(尽管使用锁定可能会执行得更糟,而且不太可预测,除非映射图很大),否则我会在需要更高级的知识之前尝试。

        6
  •  0
  •   TongChen    6 年前

    ReadWriteLock

    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();
        }
    }