这是来自集合的代码片段SynchronizedMap
。我的问题不是特定于下面的代码片段 - 而是一个通用的问题:为什么 get 操作需要同步?
public V get(Object key) {
synchronized (mutex) {return m.get(key);}
}
这是来自集合的代码片段SynchronizedMap
。我的问题不是特定于下面的代码片段 - 而是一个通用的问题:为什么 get 操作需要同步?
public V get(Object key) {
synchronized (mutex) {return m.get(key);}
}
如果您的线程只是get
从 ting 开始Map
,则不需要同步。在这种情况下,通过使用不可变映射来表达这一事实可能是一个好主意,例如来自Guava库的映射,这可以保护您在编译时不会意外修改映射。
当多个线程读取和修改映射时,麻烦就开始了,因为HashMap
Java 标准库的实现等内部结构并没有为此做好准备。在这种情况下,您可以在该地图周围包裹一个外部序列化层,例如
synchronized
关键字,SynchronizedMap
的任何地方忘记关键字,synchonized
ReadWriteLock
,这将允许多个并发读取线程(这很好)ConcurrentHashMap
,准备好被多个线程访问。但是回到你原来的问题,为什么首先需要同步:如果不看类的代码,这有点难以分辨。put
当一个线程或来自一个线程导致存储桶计数发生变化时,它可能会中断remove
,这将导致读取线程看到太多/太少的元素,因为调整大小尚未完成。也许是完全不同的东西,我不知道,这并不重要,因为它不安全的确切原因可能会随着新的 Java 版本随时改变。重要的事实只是它不受支持,并且您的代码可能会在运行时以一种或另一种方式崩溃。
通过同步 get 方法,您可以强制线程越过内存屏障并从主内存中读取值。如果您不同步 get 方法,那么 JVM 会自由地应用底层优化,这可能导致该线程幸福地读取而不知道存储在寄存器和缓存中的陈旧值。
如果表在调用过程中被调整大小get()
,它可能会在错误的存储桶中查找并错误地返回 null。
考虑发生在的步骤m.get()
:
如果另一个线程更改了映射并导致表在 2 和 3 之间调整大小,则可能会使用错误的存储桶来查找条目,从而可能给出不正确的结果。
在并发环境中需要同步的原因是,java 操作不是原子的。这意味着单个 java 操作counter++
会导致底层 VM 执行多个机器操作。
在执行这三个操作的同时,可以调用另一个名为T210
的线程并读取例如该变量的旧值。T1增加该值并将该值写11
回。但是 T2 有读值10
!如果T2也应该增加这个值,结果保持不变,即11
代替12
.
同步将避免此类并发错误。
T1:
T2: