并发HashMap merge()和put()之间的区别



我最近偶然发现一篇文章,其中阐述了ConcurrentHashMap中执行原子操作的merge方法的重要性。下面是文章的链接:

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

它说,merge方法要么在给定的键下放置新值(如果不存在(,要么用给定的值更新现有的键(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(重映射函数(,后者计算新值,而put则盲目插入/替换值。

重新映射函数接收旧值和新值,使用它们可以执行一些计算并返回新的更新值。

最新更新