Java Map.get(..)为什么或何时需要同步?
这是集合中的代码片段。我的问题并不是针对下面的代码段,而是一个通用的问题:为什么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标准库中的实现)并没有为此做
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
,我建议查看包中的类以进行并发编程。