ConcurrentHashMap及其操作

时间:2014-02-06 10:31:06

标签: java collections thread-safety

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

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

但假设一个线程正在写一个(put)到一个桶。然后,第二个线程可以同时从同一个桶读取(get)还是第二个线程必须等待put操作完成?

如果是Hashtable,则get必须等到put操作完成。但在CHM的情况下它会如何表现?

5 个答案:

答案 0 :(得分:5)

没有必要进行猜测。 source code for ConcurrentHashMap已打开,任何人都可以阅读。 (这是JDK 8 build 128,第一个JDK 8发布候选版本。)

你应该没有理解它,因为它只有6,300行。 :-)实际上,其中很大一部分是注释,大部分代码都用于处理边缘情况。 get()和put()的简单路径并不是非常复杂,只有几十行代码。

您对读取操作(get(),contains())的理解是正确的;没有阻挡。如果需要,哈希到桶并在桶内搜索是直截了当的,没有锁定。易失性读取确保了内存可见性。 (在第622-623行,val的{​​{1}}和next字段是易失性的。)读取操作与其他读取同时进行,也可以写入同一个存储桶。

删除和替换值的策略非常简单,因为在搜索和修改存储桶时,存储桶的头部被锁定。请参阅Node第1117行的synchronized块。添加到现有存储桶的replaceNode类似;请参阅put第1027行的synchronized块。这些操作当然会阻止其他线程尝试删除,替换或添加相同存储桶的条目。如果值正在被替换,则获取此键值的线程将看到旧值或新值,具体取决于读取线程是否在值被替换之前或之后找到节点写线程。

将第一个元素放入存储桶有一种特殊情况。在第1018-1020行,如果putVal发现存储桶为空,它将创建一个新节点并将CAS(比较并交换)到位。如果成功,则操作完成。如果两个线程试图将节点同时或多或少地添加到同一个桶中,则第一个的CAS将成功,而第二个的CAS将失败。但请注意,此代码位于for循环内(第1014行)。 CAS失败的线程只是循环并重试。实际上,所有其他写操作都在循环内。一般方法是操作乐观地进行,但检查并发编写器。如果乐观尝试失败,则重试该操作并根据当前更新的状态通过(可能)不同的路径。

答案 1 :(得分:1)

嗨,据我所知,ConcurrentHashMap允许多个读者同时读取而不会阻塞。这是通过基于并发级别将Map划分为不同的部分并在更新期间仅锁定Map的一部分来实现的。默认并发级别为16,因此Map分为16个部分,每个部分由不同的锁控制。这意味着,16个线程可以同时在Map上运行,直到它们在Map的不同部分上运行。这使得ConcurrentHashMap在保持线程安全完整的情况下仍然具有高性能。虽然,它带有警告。由于put(),remove(),putAll()或clear()等更新操作未同步,因此并发检索可能无法反映Map上的最新更改。

我希望这会有所帮助..

答案 2 :(得分:1)

这是来自ConcurrentHashMap类的JavaDocs:

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

答案 3 :(得分:0)

在Hashtable并发操作中将锁定整个集合,但在ConcurrentHashMap中只会锁定一个存储桶。

答案 4 :(得分:0)

来自doc:

  

支持检索的完全并发和可调整的哈希表   预期的更新并发性。本课程遵循相同的功能   规范为Hashtable,包括方法版本   对应于Hashtable的每种方法。但是,尽管如此   操作是线程安全的,检索操作不需要   锁定,并没有任何支持锁定整个表   一种阻止所有访问的方法。 此类可与之完全互操作   Hashtable在程序中依赖于其线程安全但不依赖于它   同步细节。

     

检索操作(包括get)一般不会阻塞,所以可能   与更新操作重叠(包括put和remove)。的反演   反映最近完成的更新操作的结果   坚持开始。对于诸如putAll和。之类的集合操作   清除,并发检索可能仅反映插入或删除   一些条目。同样,Iterators和Enumerations返回元素   反映哈希表的状态在某个时刻或之后的某个时刻   创建迭代器/枚举。他们不扔   ConcurrentModificationException的。但是,迭代器的设计是   一次仅由一个线程使用。

因此,您不应期望操作与Hashtable完全同步,但相同(一系列)操作是线程安全的。第二个突出显示的句子并不意味着,但在我看来强烈建议,这里发生了什么:put正在进行中,即未完成,不会阻止get - get我们根本看不到变化。

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

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.

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