Java HashMap值对象上的同步

Java HashMap值对象上的同步,java,concurrency,hashmap,Java,Concurrency,Hashmap,我有一个关于地图中对象的同步的问题(我后来更改了相同对象的值)。我希望以原子方式读取、检查并可能更新映射中的值,而不锁定整个映射。这是处理对象同步的有效方法吗 private final Map<String, AtomicInteger> valueMap = new HashMap<>(); public Response addValue(@NotNull String key, @NotNull Integer value) {

我有一个关于地图中对象的同步的问题(我后来更改了相同对象的值)。我希望以原子方式读取、检查并可能更新映射中的值,而不锁定整个映射。这是处理对象同步的有效方法吗

    private final Map<String, AtomicInteger> valueMap = new HashMap<>();

    public Response addValue(@NotNull String key, @NotNull Integer value) {
        AtomicInteger currentValue = valueMap.get(key);
        if (currentValue == null) {
            synchronized (valueMap) {
                // Doublecheck that value hasn't been changed before entering synchronized
                currentValue = valueMap.get(key);
                if (currentValue == null) {
                    currentValue = new AtomicInteger(0);
                    valueMap.put(key, currentValue);
                }
            }
        }
        synchronized (valueMap.get(key)) {
            // Check that value hasn't been changed when changing synchronized blocks
            currentValue = valueMap.get(key);
            if (currentValue.get() + value > MAX_LIMIT) {
                return OVERFLOW;
            }
            currentValue.addAndGet(value);
            return OK;
        }
    }
private final Map valueMap=new HashMap();
公共响应addValue(@NotNull字符串键,@NotNull整数值){
AtomicInteger currentValue=valueMap.get(键);
if(currentValue==null){
已同步(valueMap){
//在输入synchronized之前,再次检查值是否未更改
currentValue=valueMap.get(键);
if(currentValue==null){
currentValue=新的原子整数(0);
valueMap.put(键、当前值);
}
}
}
已同步(valueMap.get(键)){
//更改同步块时,请检查值是否未更改
currentValue=valueMap.get(键);
如果(currentValue.get()+值>最大限制){
回流溢流;
}
currentValue.addAndGet(值);
返回OK;
}
}

我看不出您的方法与标准方法有多大区别,因为
ConcurrentHashMap
已经过大量测试,并且可以配置为使用您想要运行代码的确切线程数来最小化开销

ConcurrentHashMap
中,只有在
old
值未更改时,才可以使用
(K键,V旧,V新)
方法将
自动更新为
new

由于删除所有的
原子整数
而节省的空间和由于较低的同步开销而节省的时间可能会弥补在while循环中包装
replace(k,old,new)
调用的不足:

ConcurrentHashMap<String, Integer> valueMap = 
      new ConcurrentHashMap<>(16, .75f, expectedConcurrentThreadCount);

public Response addToKey(@NotNull String key, @NotNull Integer value) {
     if (value > MAX_LIMIT) {
        // probably should set value to MAX_LIMIT-1 before failing
        return OVERFLOW; 
     }
     boolean updated = false;
     do {
        Integer old = putIfAbsent(key, value);
        if (old == null) {
             // it was absent, and now it has been updated to value: ok
             updated = true;
        } else if (old + value > MAX_LIMIT) {
             // probably should set value to MAX_LIMIT-1 before failing
             return OVERFLOW;
        } else {
             updated = valueMap.replace(key, old, old+value);
        }
     } while (! updated);

     return OK;
}
ConcurrentHashMap值映射=
新的ConcurrentHashMap(16.75f,expectedConcurrentThreadCount);
公共响应addToKey(@NotNull字符串键,@NotNull整数值){
如果(值>最大值){
//在失败之前,可能应该将值设置为MAX_LIMIT-1
回流溢流;
}
布尔更新=假;
做{
整数old=putIfAbsent(键、值);
if(old==null){
//它不存在,现在已更新为值:ok
更新=真;
}否则如果(旧+值>最大限制){
//在失败之前,可能应该将值设置为MAX_LIMIT-1
回流溢流;
}否则{
更新=值映射。替换(键、旧、旧+值);
}
}而(!更新);
返回OK;
}

此外,从好的方面来看,即使在检查密钥后将其移除,此代码也可以工作(在这种情况下,您的代码会抛出一个NPE)。

您还可以使用
ConcurrentHashMap
Collections.synchronizedMap(map)
(请参阅)同步映射。例如,在ConcurrentHashMap中更新/返回之前,如何确保在get+检查之间的值没有更改。它应该适用于更新,因为您将在更新时存储并检查上一个值,但当返回溢出时,该值有可能在返回之前已更改,除非您在代码中锁定检查和返回之间的访问权限,我不清楚,如果在没有同步的情况下访问
valueMap
,那么
currentValue
是否不会保存过时的值(如您所做)。
putIfAbsent()
不会返回布尔值。此外,我不明白为什么您要使用
get()
,检查它是否返回
null
,然后使用
putIfAbsent()
,而您可以完美地直接使用
putIfAbsent()
:如果它返回
null
,则表示该值不在映射中,否则它将返回该值。这两种情况下都为True,@magnamaga。修正了。谢谢你的输入。这似乎很好,除了获得一个初始值>MAX_LIMIT.Fixed,尽管我仍然主张将溢出值设置为MAX_LIMIT-1左右;在第一种情况下,由于溢出,
addToKey(“x”,1000)
可能会留下比
addToKey(“x”,10)
更小的值,这是违反直觉的。