Java ConcurrentHashMap及其操作

Java ConcurrentHashMap及其操作,java,collections,thread-safety,Java,Collections,Thread Safety,假设有一个ConcurrentHashMap并且有两个线程 如果两个线程都在从同一个bucket读取一些数据,那么我的理解是,两个线程都可以同时读取该bucket,因为CHM不会阻止读取操作 但是,假设一个线程正在向一个bucket写入(put)。那么,第二个线程是否可以同时从同一个bucket读取(get),或者第二个线程是否必须等待put操作完成 如果是Hashtable,则get必须等待put操作完成。但是在CHM的情况下,它将如何运行?在Hastable中,并发操作将锁定整个集合,但在C

假设有一个
ConcurrentHashMap
并且有两个线程

如果两个线程都在从同一个bucket读取一些数据,那么我的理解是,两个线程都可以同时读取该bucket,因为CHM不会阻止读取操作

但是,假设一个线程正在向一个bucket写入(
put
)。那么,第二个线程是否可以同时从同一个bucket读取(
get
),或者第二个线程是否必须等待
put
操作完成


如果是
Hashtable
,则
get
必须等待
put
操作完成。但是在CHM的情况下,它将如何运行?

在Hastable中,并发操作将锁定整个集合,但在ConcurrentHashMap中,只有一个bucket将被锁定。

据我所知,ConcurrentHashMap允许多个读卡器在没有任何阻塞的情况下并发读取。这是通过基于并发级别将映射划分为不同的部分并在更新期间仅锁定映射的一部分来实现的。默认并发级别为16,相应地,映射被分为16个部分,每个部分由不同的锁管理。这意味着,16个线程可以同时在Map上操作,直到它们在Map的不同部分上操作为止。这使得ConcurrentHashMap在保持线程安全性不变的情况下仍具有高性能。不过,它附带了警告。由于诸如put()、remove()、putAll()或clear()之类的更新操作未同步,因此并发检索可能不会反映映射上的最新更改


我希望这会有所帮助。

这是来自ConcurrentHashMap类的JavaDocs:

“检索操作(包括get)通常不会阻塞,因此可能会与更新操作(包括put和remove)重叠。检索反映了最新完成的更新操作在开始时的结果”

来自文档:

一种支持完全并发的检索和调整的哈希表 更新的预期并发性。这个类遵循相同的函数 规范为哈希表,并包括方法的版本 对应于哈希表的每个方法。然而,即使所有 操作是线程安全的,检索操作不需要 锁定,并且不支持在中锁定整个表 阻止所有访问的方法此类可与完全互操作 程序中的哈希表依赖于它的线程安全性,但不依赖于它的 同步详细信息。

检索操作(包括get)通常不会阻塞,因此 与更新操作重叠(包括放置和删除)检索 反映最近完成的更新操作的结果 开始时保持不变。用于聚合操作,如putAll和 清除、并发检索可能只反映插入或删除 一些条目。类似地,迭代器和枚举返回元素 反映哈希表在 创建迭代器/枚举。他们不会扔东西 ConcurrentModificationException。然而,迭代器被设计成 一次只能由一个线程使用

因此,您不应该期望操作与哈希表完全同步,但相同的(一系列)操作是线程安全的。第二个突出显示的句子并不意味着,但在我看来,它强烈地暗示了这里正在发生的事情:正在进行的
put
,即未完成,不会阻止
get
get
将根本看不到更改

虽然我还没有完成整个CHM类,但这篇文档支持我的假设(摘自OpenJDK 6)

静态最终类段扩展ReentrantLock实现可序列化{
/*
*段维护一个条目列表表,这些条目列表总是
*保持一致状态,因此可以读取(通过volatile
*读取段和表)而不锁定。此
*在表期间需要复制节点
*调整大小,以便读者可以遍历旧列表
*仍然使用旧版本的表。

当更新“完成”时,似乎没有明确定义;通常,只要新的bucket链接到bucket列表中,我猜。CHM还大量使用volatile字段,以确保线程读取列表中最新的bucket。

不需要推测。该字段是开放的,任何人都可以读取它。(这是JDK 8 build 128,第一个JDK 8候选版本。)

您理解它应该不会有问题,因为它只有6300行长。:-)事实上,其中很大一部分是注释,大部分代码用于处理边缘情况。get()和put()的直接路径并不十分复杂,只有几十行代码

您对读取操作(get(),contains())的理解是正确的;没有阻塞。对存储桶进行散列并在存储桶内搜索(如果需要)是简单的,没有锁定。通过易失性读取确保内存可见性。(在第622-623行,
Node
val
next
字段是不稳定的。)读取操作与其他读取操作以及对同一存储桶的写入操作同时进行

删除和替换值的策略非常简单,因为在搜索和修改存储桶时,存储桶的头部被锁定。请参阅
replaceNode
第1117行的
synchronized
块。添加到现有存储桶的
put
类似;请参阅
synchronized
块在
putVal
的第1027行。这些操作当然会阻止其他试图删除、替换或向同一存储桶添加条目的线程。如果某个值正在被替换,则获取该键值的线程将看到
static final class Segment<K,V> extends ReentrantLock implements Serializable {
    /*
     * Segments maintain a table of entry lists that are always
     * kept in a consistent state, so can be read (via volatile
     * reads of segments and tables) without locking.  This
     * requires replicating nodes when necessary during table
     * resizing, so the old lists can be traversed by readers
     * still using old version of table.