java 是否会以一致的方式对 ConcurrentHashMap 的值求和?
Will java streams sum values of a ConcurrentHashMap in an consistent manner?
我有一个 concurrentHashMap 实例,一些线程向其添加条目。这些值是整数。
同时,其他线程希望检索映射中所有值的总和。我希望这些线程看到一致的值。但是,他们不需要总是看到最新的值。
下面的代码线程安全吗?
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();
}
}
是否会在一组一致的值上计算求和运算?
求和时,调用put()会被阻塞吗?
当然我可以自己同步访问,甚至拆分读写锁以允许并发读访问和同步写访问,但我很好奇在使用 concurrentHashMap 作为集合实现时是否有必要。
ConcurrentHashMap
的要点是条目尽可能相互独立。整个地图的视图不一致。事实上,即使 size
也不是 return 一个非常有用的值。
documentation关于ConcurrentHashMap
的keySet()
和entrySet()
的说法:视图的迭代器和拆分器弱一致。
- 它们可能会与其他操作同时进行
- 他们永远不会抛出 ConcurrentModificationException
- 它们保证在构造时只遍历元素一次,并且可能(但不保证)反映构造后的任何修改。
所以...
Is the following code thread safe?
是的,狭义上不存在 ConcurrentModificationException 或 HashMap 的内部不一致。
Will the sum operation be calculated on a consistent set of values?
在弱一致性集合上
When the sum operation is happening, will calls to put() be blocked?
没有
如果您需要同时查询总和,一种解决方案是编写一个包装器 class 来维护地图的状态和总和,使用 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();
}
}
这有一个额外的好处,即在 O(1) 而不是 O(n) 时间内查询总和。如果愿意,您可以添加更多 Map
方法,甚至可以实现 Map<Integer, Integer>
- 当您以任何方式更改地图内容时,请注意保持总和。
我有一个 concurrentHashMap 实例,一些线程向其添加条目。这些值是整数。
同时,其他线程希望检索映射中所有值的总和。我希望这些线程看到一致的值。但是,他们不需要总是看到最新的值。
下面的代码线程安全吗?
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();
}
}
是否会在一组一致的值上计算求和运算?
求和时,调用put()会被阻塞吗?
当然我可以自己同步访问,甚至拆分读写锁以允许并发读访问和同步写访问,但我很好奇在使用 concurrentHashMap 作为集合实现时是否有必要。
ConcurrentHashMap
的要点是条目尽可能相互独立。整个地图的视图不一致。事实上,即使 size
也不是 return 一个非常有用的值。
documentation关于ConcurrentHashMap
的keySet()
和entrySet()
的说法:视图的迭代器和拆分器弱一致。
- 它们可能会与其他操作同时进行
- 他们永远不会抛出 ConcurrentModificationException
- 它们保证在构造时只遍历元素一次,并且可能(但不保证)反映构造后的任何修改。
所以...
Is the following code thread safe?
是的,狭义上不存在 ConcurrentModificationException 或 HashMap 的内部不一致。
Will the sum operation be calculated on a consistent set of values?
在弱一致性集合上
When the sum operation is happening, will calls to put() be blocked?
没有
如果您需要同时查询总和,一种解决方案是编写一个包装器 class 来维护地图的状态和总和,使用 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();
}
}
这有一个额外的好处,即在 O(1) 而不是 O(n) 时间内查询总和。如果愿意,您可以添加更多 Map
方法,甚至可以实现 Map<Integer, Integer>
- 当您以任何方式更改地图内容时,请注意保持总和。