Java:如何自动替换Map中的所有值?

时间:2018-05-14 13:05:27

标签: java multithreading dictionary concurrency

我在多线程环境中有一个有状态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个值。

6 个答案:

答案 0 :(得分:1)

最简单,最简单的方法是切换地图而不是替换地图内容。无论是使用volatile还是AtomicReference(我不明白为什么你特别需要AtomicReferenceFieldUpdater),不应该产生太多差异。

这可确保您的地图始终处于正确状态,并允许您提供快照。但它并不能保护您免受其他并发问题的影响,因此如果丢失更新等问题是您需要的更多代码(尽管AtomicReference将为您提供CAS方法来处理这些问题)。

如果您只考虑地图的完整原子替换,问题实际上相当简单。知道其他操作对地图的影响以及如何影响将是有益的。我还想了解为什么ConcurrentSkipListMap被选为ConcurrentHashMap

答案 1 :(得分:0)

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

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;

答案 2 :(得分:0)

由于地图非常小,所以只需在您访问它的所有地方使用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");
}

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

答案 3 :(得分:0)

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

除非出现任何重大的性能问题(尽管除非地图很大,否则使用锁定可能会表现得更差,而且可预测性更差)我会在需要更高级知识的任何事情之前尝试锁定。

功能程序员如何做到这一点。

答案 4 :(得分:0)

使用CAS和AtomicReference的方法是在每次批量更新时复制地图内容。

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

此地图可以是并发的,但对于&#34;批量更新&#34;它是只读的。然后在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);
}

答案 5 :(得分:0)

使用ReadWriteLock可以自动替换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();
    }
}