【问题标题】:Can ConcurrentHashMap put() method be not thread-safe?ConcurrentHashMap put() 方法可以不是线程安全的吗?
【发布时间】:2021-04-27 02:30:21
【问题描述】:

假设我有一个名为 map 的共享 ConcurrentHashMap 已经只有一个映射 ("One", 1),并假设我有 2 个线程。

第一个线程执行这段代码:

map.put("One", 2);

第二个线程执行这段代码:

synchronized (map) {
    Integer number = map.get("One");
    System.out.println(number == map.get("One"));
}

由于 ConcurrentHashMap 使用锁定条带方法而不是锁定整个对象,我不认为所描述的场景是线程安全的。 特别是我不知道Integer number = map.get("One"); 调用和第二个线程中的System.out.println(number == map.get("One")); 调用之间的第一个线程中是否可能存在map.put("One", 2); 交错,尽管两者都在同步块内。

那么该代码是否有可能打印错误?

【问题讨论】:

  • 您的理解是正确的,代码可能打印错误。没有什么能阻止线程在这里交错。但是,这并不意味着 put() 本身不是线程安全的(基于您问题的标题)。
  • @PanagiotisBougioukos 这个问题的重点是get 被连续调用两次,以及如何防止导致两个不同的结果。同步对于防止这种情况是必要的,但提供的代码中的同步不会这样做,因为它没有使用与 put 方法相同的锁进行同步。
  • 更一般地,您可能会问,“线程安全的类可以以非线程安全的方式使用吗?”答案是响亮的“是的!”完全使用线程安全组件构建程序并不能自动保证您的程序是线程安全的。
  • 继@SolomonSlow 的评论之后:密切关注“线程安全”的含义很重要:从内部状态的角度来看,CHM 是线程安全的;但是您使用该 CHM 的代码可能有其自己的、单独的不变量,它们具有单独的线程安全要求。在这种情况下,您的代码需要确保在多个线程访问时正确保留不变量。
  • 请注意,ConcurrentHashMap 在 Java 7 及更早版本中使用了锁分条。从 Java 8 开始,它不使用锁条带化。但即使在那些旧版本中,一个普通的get 调用在理想情况下也不会锁定任何东西。

标签: java multithreading concurrency synchronization concurrenthashmap


【解决方案1】:

ConcurrentHashMap 中的所有方法都可能是线程安全的,但这并不意味着它在ConcurrentHashMap 对象本身上同步。您可以做的是同步put 和同一参考上的地图访问代码。您的put 代码必须更改为:

synchronized (map) {
    map.put("One", 2);
}

您的访问代码可以保持如下:

synchronized (map) {
    Integer number = map.get("One");
    System.out.println(number == map.get("One"));
}

这将永远无法打印false

【讨论】:

  • 但是在这种情况下,有一个 ConcurrentHashMap 是没用的
  • 没错,当您只想同步对Map 的单方法访问时,ConcurrentHashMap 很方便。在这种情况下,如果这些都是您对该地图的访问权限,那么您不妨使用HashMap。最佳做法是将get 的结果存储在一个变量中,并使用该变量两次。这样,ConcurrentHashMap 就足够同步了。
  • @SolomonSlow 好吧,Edoardo 说“在这种情况下”,这是正确的。 在这种情况下你可以使用普通的HashMapsynchronized
猜你喜欢
  • 1970-01-01
  • 2011-09-30
  • 1970-01-01
  • 2011-04-15
  • 1970-01-01
  • 2012-08-20
  • 2019-09-22
  • 2018-01-31
  • 2019-05-18
相关资源
最近更新 更多