【问题标题】:Why does HashMap.get(key) needs to be synchronized when change operations are synchronized?为什么同步变更操作时需要同步HashMap.get(key)?
【发布时间】:2015-08-30 18:56:12
【问题描述】:

我在一个 HashMap 上使用来自多个线程的 .get(...).put(...).clear() 操作。 .put(...).clear()synchronized 块内,但 .get(...) 不在。我无法想象这会导致问题,但在我看到的其他代码中.get() 几乎总是同步的。

get/put相关代码

Object value = map.get(key);
if(value == null) {
  synchronized (map) {
    value = map.get(key); // check again, might have been changed in between
    if(value == null) {
      map.put(key, new Value(...));
    }
  }
}

清晰就是:

synchronized (map) {
  map.clear();
}

由于同步,写入操作将使缓存无效,get(...) 返回 null 或实例。通过将.get(...) 操作放入synchronized(map) 块中,我真的看不出会出现什么问题或有什么改进。

【问题讨论】:

  • 这就是创建 java.util.concurrent.locks.ReadWriteLock 的原因。

标签: java multithreading synchronization


【解决方案1】:

这是一个简单的场景,会在不同步的get 上产生问题:

  • 线程 A 启动 get,计算哈希桶编号,并被抢占
  • 线程 B 调用 clear(),因此分配了较小的存储桶数组
  • 线程 A 被唤醒,并可能遇到 index-out-of-bounds 异常

这是一个更复杂的场景:

  • 线程 A 锁定地图以进行更新,并被抢占
  • 线程 B 发起 get 操作,计算哈希桶编号,并被抢占
  • 线程 A 唤醒,继续 put,并意识到桶需要调整大小
  • 线程 A 分配新的存储桶,将旧内容复制到其中,并添加新项目
  • 线程 B 唤醒,并使用新的存储桶数组中的旧存储桶索引继续搜索。

此时,A 可能不会找到正确的项目,因为它很可能在不同索引的哈希桶中。这就是为什么get 也需要同步的原因。

【讨论】:

  • 以上所有情况都可能发生在程序的顺序一致执行中,但Java 甚至不保证SC。因此,即使这里描述的所有问题都可以通过某种方式避免,get仍然必须同步。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-09-29
  • 2019-02-20
  • 2015-04-05
  • 2020-06-05
  • 1970-01-01
相关资源
最近更新 更多