Concurrent HashMap merge() 和 put() 的区别

Difference between Concurrent HashMap merge() and put()

我最近偶然看到一篇文章,其中阐述了合并方法在 ConcurrentHashMap 中执行原子操作的重要性。以下是文章的 link:

https://www.nurkiewicz.com/2019/03/mapmerge-one-method-to-rule-them-all.html

它表示合并方法要么将新值置于给定键下(如果不存在),要么使用给定值 (UPSERT) 更新现有键并解释此概念。 然而,这正是 put() 方法所做的。

ConcurrentHashMap 中的 get() 和 put() 不是线程安全的。

请指导我了解 merge() 如何处理 get() 和 put() 在一起的情况并使 ConcurrentHashMap 上的操作线程安全?

并发代码的一个关键概念是,两个原子操作一个接一个地执行不一定与单个原子操作执行相同的操作具有相同的效果。

让我们忽略地图尚未包含值的情况,因为这是不太有趣的一种情况(您仍然需要处理竞争条件,但另一种情况更有趣)。

因此,如果给定键已经有值,则 merge 基本上变为 put(key, mergeFunction(newValue, get(key))

如果您以这种方式实现它,那么您可能 运行 陷入丢失更新的非常现实的问题:

  1. 您的线程执行 get(key) 并获取当前值(我们称之为 v1)
  2. 另一个 线程将键的绑定更新为新值(我们称之为 v2)
  3. 您的线程使用 v1 和您的合并参数(我们称之为 v3)计算新的更新值
  4. 您的线程将键的绑定更新为新合并的值 v3

请注意,对 v2 的更新基本上会被您的代码忽略(覆盖)。如果映射值旨在表示计数器,则意味着您基本上完全忽略了一个更新,并且会得到错误的结果。

merge 提供了一种将更新应用到现有值的方法,而不必担心其他并发更新会改变您的值而您丢失更新。

你遗漏的一点是 merge 方法接受一个 BiFunction (一个 remappingFunction),它计算新值,而 put 盲目 inserts/replaces一个值。

remappingFunction 接收到旧值和新值,您可以使用它们执行一些计算和 return 新的更新值。