computeifpresent 实现“有键才动、动则原子”,仅当键存在且值非 null 时执行函数,返回 null 则删除该键,天然线程安全。

直接用 computeIfPresent 实现“有键才动、动则原子”,不用先 get 再 put,也不用自己加锁或判空。
明确触发前提:键必须存在,且当前值不能为 null
这个方法不会响应两种情况:键根本不在 map 里;键在但值是 null。它内部会先检查 containsKey(key),再取值 v = get(key),仅当 v != null 时才执行你的函数。所以如果你之前设过 map.put("k", null),后续对 "k" 调用 computeIfPresent 就会静默跳过——这不是 bug,是设计使然。
- 确保业务中“键存在”和“值非 null”是一致的语义(比如用户注册后才初始化积分,就不会出现 key 存在但 score 为 null)
- 若需兼容“键存在但值为 null”的场景,改用
compute()并在 lambda 中显式判断v == null - 在
ConcurrentHashMap中,该操作天然线程安全,适合高并发计数、状态更新等场景
典型更新模式:累加、条件变更、校验后覆盖
函数签名是 (K key, V oldValue) -> newValue,返回值决定最终行为:非 null 值用于替换原值;返回 null 则自动移除该键。
- 安全累加:只对已存在的用户增加订单数
map.computeIfPresent("uid", (k, v) -> v + 1) - 条件更新:绩效 ≥ 85 才涨薪 10%,否则维持原工资
map.computeIfPresent("eid", (k, salary) -> perfMap.get(k) >= 85 ? salary * 1.1 : salary) - 校验删除:只保留非空配置项,空字符串就清理
config.computeIfPresent("log.path", (k, v) -> v != null && !v.trim().isEmpty() ? v : null)
避开常见陷阱:返回值语义与线程安全边界
它的原子性体现在“读-计算-写”整个流程不可分割,但计算函数内部不自动受保护。如果函数里访问了外部共享变量,仍需自行同步。
- 不要在 lambda 里调用
map.put()或其他修改 map 的操作,可能触发ConcurrentModificationException(尤其在ConcurrentHashMap中) - 返回
null是合法行为,代表“删键”,不是出错信号;若本意是设为 null 值,应改用compute() - 对比
computeIfAbsent(键不存在时插入)和compute(无论键存不存在都执行),computeIfPresent意图最聚焦:只动老数据,不碰新键
对比手写逻辑:少三行,多一层保障
传统写法要查、要判、要改:
Integer old = map.get(key);
if (old != null) {
map.put(key, old + 1);
}
而一行 computeIfPresent 不仅更简洁,还在并发下避免了中间状态被其他线程篡改的风险。在 ConcurrentHashMap 中,这相当于一次无锁 CAS 更新。
- 代码更短,意图更直白:“已有键才更新”一目了然
- 避免
get和put之间的竞态窗口(例如两次调用间值被另一线程清空) - 天然适配函数式风格,便于单元测试(函数可单独提取、验证)










