You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何向ConcurrentHashMap中原子性地条件更新值?

游戏最高分的ConcurrentHashMap原子更新问题

问题背景

我维护着一个存储不同游戏最高分的映射表,收到新分数时要判断是否高于当前最高分,是的话就更新成新最高分。

选了ConcurrentHashMap来存,但发现原子性检查+条件更新的实现有点麻烦:

  • 试了compute方法,本来想返回null保留原条目,结果直接把条目删了(示例代码和运行结果如下)。
  • 如果返回当前值,又怕触发没必要的更新,毕竟ConcurrentHashMap的优势就是高频读取能并发,只有实际更新时才同步、建立先行关系。

示例代码:

import java.util.concurrent.ConcurrentHashMap;

public class ConcHashMapTest{

    public static void main(String[] args){
        ConcurrentHashMap<String,String> h = new ConcurrentHashMap<>();
        
        h.put("TEST","value1");
        
        /*
         问题是,能不能原子性地检查值,只在需要的时候更新?
         
         我知道可以用compute,但必须要修改Map吗?返回null能不能保留原映射?
         */
        
        h.compute("TEST",(k,v) -> {
            if (v.equals("changeme")){
                return "CHANGED";
            }
            else {
                return null;
            }
        });
        
        System.out.println("Now hashmap is "+h);
    }
}

运行结果:

Now hashmap is {}

后来的思考:

  • 搞清楚了ConcurrentHashMap的优势是高频读取时的并发能力,要原子更新就得同步,但compute只会锁对应的桶,不是整个Map,那返回原值是不是正确做法?
  • 最后我倾向于不追求原子性了:每秒几千个分数里真正的高分极少,两个高分同时到导致代码交错的概率几乎为零,就用下面这段代码,接受理论上可能漏记最高分的漏洞:
if (score > map.get("SCORE")){
  map.put("SCORE",score);
}

可行解决方案

1. 正确用compute实现原子更新

不需要更新的时候直接返回当前值v就行,ConcurrentHashMap内部会判断返回值和原值是否相等,只有真的变了才会执行更新、建立先行关系,不会有多余操作。

针对最高分场景的代码:

map.compute(gameId, (k, currentScore) -> {
    // 处理游戏第一次提交分数的情况(key不存在)
    if (currentScore == null) {
        return newScore;
    }
    // 新分数更高就更新,否则返回原值
    return newScore > currentScore ? newScore : currentScore;
});

这种方式完全保证原子性,只有新分数确实更高时才会触发实际更新,不影响并发读取的性能。

2. 用merge更简洁地实现原子更新

如果你的场景是“只有新值满足条件才替换旧值”,merge更合适——key不存在就插入新值,存在的话就用传入的函数计算结果。

最高分场景的代码:

// 第三个参数是BiFunction,key存在时用旧值和新值计算最终结果
map.merge(gameId, newScore, (oldScore, newScore) -> 
    newScore > oldScore ? newScore : oldScore
);

merge也是原子操作,内部只锁对应的桶,不会影响整个Map的并发读取。

3. 非原子方案的风险说明

你说的先get再put的非原子方案,确实有极小概率的竞态问题:比如两个线程同时读到同一个旧最高分,然后都执行put,最后可能是较低的那个高分覆盖了真正的最高分。

如果业务能接受这种理论上的极小概率错误,这个方案确实最简单高效,完全没锁操作,读写都快。

内容的提问来源于stack exchange,提问作者Bruce

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 12:44:50