简体   繁体   English

java 会以一致的方式对 ConcurrentHashMap 的值求和吗?

[英]Will java streams sum values of a ConcurrentHashMap in an consistent manner?

I have a concurrentHashMap instance that some threads add entries to.我有一个 concurrentHashMap 实例,一些线程向其中添加了条目。 The values are integers.这些值是整数。

Simultaneously, other threads wish to retrieve the sum of all the values in the map.同时,其他线程希望检索映射中所有值的总和。 I wish that these threads see a consistent value.我希望这些线程看到一致的值。 However, it doesn't need to be such that they always see the latest value.但是,它们不必总是看到最新的值。

Is the following code thread safe?以下代码线程安全吗?

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class MyClass {
    private Map<Integer, Integer> values = new ConcurrentHashMap<>();

    public void addValue(Integer key, int value){
        values.put(key, value);
    }

    public long sumOfValues(){
        return values
                .values()
                .stream()
                .mapToInt(Integer::intValue)
                .sum();
    }
}

Will the sum operation be calculated on a consistent set of values?求和运算会在一组一致的值上计算吗?

When the sum operation is happening, will calls to put() be blocked?当 sum 运算发生时,对 put() 的调用会被阻塞吗?

Of course I could synchronize the access myself, and even split the read and write locks to allow for concurrent read access and synchronized write access, but I am curious if its necessary when using concurrentHashMap as the collection implementation.当然我可以自己同步访问,甚至可以拆分读写锁以允许并发读访问和同步写访问,但是我很好奇在使用 concurrentHashMap 作为集合实现时是否有必要。

The documentation says about ConcurrentHashMap 's keySet() and entrySet() : The view's iterators and spliterators are weakly consistent. 文档说明了ConcurrentHashMapkeySet()entrySet()视图的迭代器和拆分器弱一致。

Weakly consistent characterized as弱一致特征为

  • they may proceed concurrently with other operations他们可以与其他操作同时进行
  • they will never throw ConcurrentModificationException他们永远不会抛出 ConcurrentModificationException
  • they are guaranteed to traverse elements as they existed upon construction exactly once, and may (but are not guaranteed to) reflect any modifications subsequent to construction.它们保证遍历元素,因为它们在构造时就存在过一次,并且可能(但不保证)反映构造后的任何修改。

So...所以...

Is the following code thread safe?以下代码线程安全吗?

Yes, in the narrow sense of absent ConcurrentModificationException or internal inconsistencies of the HashMap.是的,狭义上没有 ConcurrentModificationException 或 HashMap 的内部不一致。

Will the sum operation be calculated on a consistent set of values?求和运算会在一组一致的值上计算吗?

on a weakly consistent set在弱一致集上

When the sum operation is happening, will calls to put() be blocked?当 sum 运算发生时,对 put() 的调用会被阻塞吗?

No

The point of ConcurrentHashMap is that the entries are as independent from one another as possible. ConcurrentHashMap的要点是条目尽可能相互独立。 There isn't a consistent view of the whole map.对整个地图没有一致的看法。 Indeed, even size doesn't return a very useful value.事实上,即使size也不会返回一个非常有用的值。

If you need to query the sum concurrently, one solution is to write a wrapper class which maintains both the map's state and the sum, using a LongAdder to atomically maintain the sum.如果您需要同时查询总和,一种解决方案是编写一个包装类来维护地图的状态和总和,使用LongAdder原子地维护总和。

import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.LongAdder;

public class MapSum {
    private final ConcurrentMap<Integer, Integer> map = new ConcurrentHashMap<>();
    private final LongAdder sum = new LongAdder();

    public Integer get(Integer k) {
        return map.get(k);
    }

    public Integer put(Integer k, Integer v) {
        Integer[] out = new Integer[1];
        map.compute(k, (_k, old) -> {
            out[0] = old;
            // cast to long to avoid overflow
            sum.add((long) v - (old != null ? old : 0));
            return v;
        });
        return out[0];
    }

    public Integer remove(Integer k) {
        Integer[] out = new Integer[1];
        map.compute(k, (_k, old) -> {
            out[0] = old;
            // cast to long to avoid overflow; -Integer.MIN_VALUE == Integer.MIN_VALUE
            if(old != null) { sum.add(- (long) old); }
            return null;
        });
        return out[0];
    }

    public long sum() {
        return sum.sum();
    }
}

This has the added benefit of querying the sum in O(1) instead of O( n ) time.这具有在 O(1) 而不是 O( n ) 时间内查询总和的额外好处。 You can add more Map methods if you like, and even implement Map<Integer, Integer> - just be careful to maintain the sum when you change the map's contents in any way.如果愿意,您可以添加更多Map方法,甚至可以实现Map<Integer, Integer> - 当您以任何方式更改地图内容时,请小心维护总和。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM