Java Map.get(..)为什么或何时需要同步?

Java Map.get(..)为什么或何时需要同步?,java,collections,concurrency,synchronization,Java,Collections,Concurrency,Synchronization,这是集合中的代码片段。我的问题并不是针对下面的代码段,而是一个通用的问题:为什么get操作需要同步 public V get(Object key) { synchronized (mutex) {return m.get(key);} } 如果您的线程仅从映射中获取,则不需要同步。在这种情况下,最好使用不可变映射(如库中的映射)来表达这一事实,这样可以保护您在编译时不会意外修改映射 当多个线程读取和修改映射时,问题就开始了,因为映射的内部结构(例如Java标准库中的实现)并没有为此做

这是集合中的代码片段。我的问题并不是针对下面的代码段,而是一个通用的问题:为什么get操作需要同步

public V get(Object key) {
    synchronized (mutex) {return m.get(key);}
}

如果您的线程仅从
映射中获取
,则不需要同步。在这种情况下,最好使用不可变映射(如库中的映射)来表达这一事实,这样可以保护您在编译时不会意外修改映射

当多个线程读取和修改映射时,问题就开始了,因为映射的内部结构(例如Java标准库中的实现)并没有为此做好准备。在这种情况下,您可以将外部序列化层环绕在该映射上,如

  • 使用
    synchronized
    关键字
  • 稍微安全一点的方法是使用,因为这样你就不会忘记
    synchonized
    关键字,无论它在哪里需要
  • 使用允许多个并发读取线程的
  • 切换到一个totally,它为多个线程访问做好了准备

但是回到最初的问题,为什么首先需要同步:如果不看类的代码,这有点难以判断。当一个线程中的
put
remove
导致存储桶计数更改时,它可能会中断,这将导致读取线程看到太多/太少的元素,因为调整大小尚未完成。也许有些完全不同的东西,我不知道,也不是很重要,因为它不安全的确切原因可以在新的Java版本中随时改变。重要的事实是它不被支持,并且您的代码可能会在运行时炸毁一种或另一种方式。

< P>如果在调用<代码> GET()/Cube >的过程中调整表的大小,它可能会查看错误的桶并错误地返回NULL。 考虑在
m.get()中发生的步骤:

  • 将为密钥计算哈希
  • 读取表的当前长度(HashMap中的bucket)
  • 该长度用于计算从表中获得的正确铲斗
  • 检索存储桶并遍历存储桶中的条目,直到找到匹配项或到达存储桶的末端

  • 如果另一个线程更改映射并导致表的大小在2到3之间,则可能会使用错误的bucket来查找条目,从而可能导致错误的结果。

    在并发环境中需要同步的原因是java操作不是原子的。这意味着像
    counter++
    这样的单个java操作会导致底层VM执行多个机器操作

  • 读取值
  • 增值
  • 写值
  • 在执行这三个操作时,可以调用另一个名为T2的线程并读取该变量的旧值,例如
    10
    T1增加该值并将值
    11
    写回。但是T2的读取值
    10
    !在T2也应增加该值的cas中,结果保持不变,即
    11
    而不是
    12

    同步将避免此类并发错误

    T1:

  • 设置同步器令牌
  • 读取值
  • 调用了另一个线程T2并尝试读取该值。但是由于同步器令牌已经设置,T2必须等待
  • 增值
  • 写值
  • 删除同步器令牌
  • T2:

  • 设置同步器令牌
  • 读取值
  • 增值
  • 写值
  • 删除同步器令牌

  • 通过同步get方法,您可以强制线程越过内存屏障并从主内存读取值。如果您不同步get方法,那么JVM可以自由地应用底层优化,这可能会导致线程读取时不知道寄存器和缓存中存储的过时值

    因为它可能同时被多个线程访问。是的,但为了使问题更具体,在代码的哪一部分会有问题?它会在散列过程中发生吗?在此调用过程中,线程在哪里发生冲突?它们是偶数还是不偶数?想象两个线程:T1不断地从映射中获取值,T2在映射中放置/替换数据。两个线程并行运行。有一个键K,T2将在T1检索K值的同一时刻刷新K的值。应该首先执行哪个键?T1应该检索哪个值,旧值还是新值?清晰的解释。很好,先生。处理这个问题的所有选项+1。连同
    ConcurrentHashMap
    ,我建议查看包中的类以进行并发编程。