【问题标题】:Can concurrntHashMap guarantee true thread safety and concurrency at the same time?concurrntHashMap 能否同时保证真正的线程安全和并发?
【发布时间】:2011-09-15 03:38:45
【问题描述】:

我们知道 ConcurrentHashMap 可以提供对多个线程的并发访问以提高性能,并且在这个类中,段是同步的(我说的对吗?)。问题是,这种设计能保证线程安全吗?假设我们有 30 多个线程访问和更改由 ConcurrentHashMap 实例中的相同键映射的对象,我的猜测是,他们仍然需要排队,不是吗?

根据我的回忆,“Java 并发实践”一书说 ConcurrentHashMap 提供并发读取和相当水平的并发写入。在上述场景中,如果我的猜测是正确的,那么性能不会比使用 Collection 的静态同步包装器 api 更好吗?

感谢您的澄清, 约翰

【问题讨论】:

  • [我将我的答案放在评论中,因为我不能 100% 确定我声称的内容。] ConcurrentHashMap 比包装器更快的原因有两个:A) ConcurrentHashMap 可以 read 不加锁,B) 它是用并发原子变量构造的,它使用较新的 cpu 缓存同步操作(“条件更新”),这可以比旧的同步方法快得多。

标签: java multithreading concurrency concurrenthashmap


【解决方案1】:

您仍然必须同步对正在修改的对象的任何访问,并且您怀疑对同一密钥的所有访问仍然存在争用。性能提升来自于对不同密钥的访问,这当然是更典型的情况。

【讨论】:

  • +1 用于实际阅读问题(与我的回答不同,叹息)。
  • "...仍然必须同步..." 在大多数 ConcurrentHashMap 的用法中这是错误的。请参阅 Jed 的回答。
  • @irreputable:我认为这个答案是指单个映射值对象(而不是映射本身)的并发突变,这当然 ConcurrentHashMap 根本无济于事。这个问题不是很清楚,但这就是它在第一段中提出的问题。
  • @Colin 问题是并发映射的大多数用法是否会在其中存储可变对象。我的经验是没有。使用并发地图的人有意识地或本能地在其中存储不可变数据,并且通过更新地图而不是值来发布更改。
  • 问题指出 30 多个线程将访问和更改对象,因此它们必须是可变的。
【解决方案2】:

ConcurrentMap 可以为您提供并发性的是,对映射本身的修改是原子完成的,并且任何写入发生在任何读取之前(这很重要,因为它提供了安全的发布地图上的任何参考。

安全发布意味着从地图中检索到的任何(可变)对象都将在其被放置在地图中之前被看到。但是,它对发布检索后所做的修改没有帮助。

但是,如果您有被多方修改的可变对象,那么并发性和线程安全性通常很难推理和纠正。通常,您必须锁定才能使其正确。更好的方法通常是将不可变对象与ConcurrentMap 条件 putIfAbsent/replace 方法结合使用,并以这种方式线性化您的算法。这种无锁风格往往更容易推理。

【讨论】:

  • 另一个选项,读取值,如果需要修改 clone()/复制它,然后在完成后使用 CHM.replace(key, oldvalue, newValue)。当然它需要 CAS 类似的循环。困难的部分是算法必须准备的替换 b/c 失败。 ...这需要内部状态等。我猜锁定+putIfAbsent 对大多数人来说似乎更容易。
【解决方案3】:

问题是,这种设计能保证线程安全吗?

保证map的线程安全;也就是说,在多个线程同时执行更新的情况下,地图上的访问和更新具有明确且有序的行为。

它确实保证了键或值对象的线程安全。而且它不提供任何形式的更高级别的同步。

假设我们有 30 多个线程访问和更改由 ConcurrentHashMap 实例中的相同键映射的对象,我的猜测是,他们仍然需要排队,不是吗?

如果你有多个线程尝试使用同一个键,那么它们的操作将不可避免地在某种程度上被序列化。这是不可避免的。

事实上,通过简要查看源代码,ConcurrentHashMap 似乎会在对映射的特定段有太多争用时回退到使用传统锁。如果您有多个线程同时尝试访问和更新同一个密钥,则会触发锁定。

【讨论】:

  • 我正在阅读 JDK 6 并且有。但它不在外部 get 中。它是段哈希表上的get
  • @irreputable Java 6 实际上会在 JVM 重新排序构造对象但稍后初始化 volatile 值的情况下进行一些锁定。这在 Java 6 中实际上不会发生,但从未被删除(在 Java 5 中是可能的)。话虽如此,Java 7 的实现实际上是对整个类的一个相当不错的改变。但由于 Java 7 不会再出现 null volatile 值问题(在 6 中解决),所以它始终是 volatile 负载并且没有锁
  • @John 新的 java 内存模型从 Java 5 开始生效,而不是 6。你指的是这个吗?
  • @irreputable 你对 Java 5 是关于内存模型的介绍是正确的。我指的是,当 Java 6 发布时,Java 5 中强制读取锁定的问题不再发生。
【解决方案4】:

首先要记住,线程安全工具本身并不能保证线程安全地使用它

例如 putIfAbsent 的 if(!map.contains(k))map.put(k,v); 构造不是线程安全的

并且每个值的访问/修改仍然必须独立地进行线程安全

【讨论】:

    【解决方案5】:

    读取是并发的,即使对于相同的键也是如此,因此对于典型应用程序的性能会更好。

    【讨论】:

      猜你喜欢
      • 2013-03-01
      • 2013-01-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-09-25
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多