ConcurrentHashMap 在 Java 中是一个线程安全的哈希表,它通过分段锁(Segmentation)的概念来允许并发访问。从Java 8开始,其内部实现发生了变化,以进一步优化性能。下面是这两个版本的简要说明:
Java 7 及之前版本:
在 Java 7 及以前版本中,ConcurrentHashMap 使用分段锁(Segments),类似于一个内部的哈希表数组,每个哈希表就是一个Segment。每个Segment 管理着它自己的一部分数据,并且每个Segment都有自己的锁。当需要put或get操作时,根据键值计算得到对应的Segment,然后只对该Segment加锁。这意味着多线程操作不同Segment时可以同时进行,因此大幅度提高了并发性能。这里不具体展开对Java 7之前版本的讨论。
Java 8 及之后版本:
在JDK 8及之后的版本中,ConcurrentHashMap实现线程安全的关键在于它采用了细粒度的锁机制以及无锁的操作来降低锁竞争,从而允许多个线程并发地操作不同数据段。以下是一些详细的策略:
-
CAS(Compare-And-Swap):
-
细粒度的锁定: -
链表转红黑树:
-
分离的读写操作:
-
synchronized关键字:
-
弱一致性迭代器:
-
桶初始化和扩容的延迟加载:
对于某些关键变量的更新操作,ConcurrentHashMap使用原子的CAS操作,这是一种乐观锁技术。CAS会在不阻塞其他线程的情况下进行变量更新:它比较当前变量的值是否与预期的旧值相等,如果相等则更新为新值。这样可以避免使用传统的互斥锁。
ConcurrentHashMap中的每个桶条目都可以独立加锁,只有当线程需要对特定节点进行修改时,才会对该节点加锁。这意味着同时只锁定了数据结构的一小部分,从而使得其他线程可以访问和修改其他未被锁定的节点。
如果桶中的链表过长,将其转换为平衡的红黑树,可以提高搜索效率。在转换过程中可能需要锁定相关的链表部分,但是读取操作通常不受影响。
大部分读操作(如get()方法)通常是无锁的,因为它们可以安全地读取到最新的、由写操作保证已正确发布的值。写操作(如put()或remove())则需确保通过锁定特定的桶来实现线程安全。
JDK 8改进了锁机制,在节点级别上使用内置锁(即synchronized关键字)。当更新节点时,只有当前节点被锁定,其他节点的操作仍然可以并行执行。
ConcurrentHashMap中的迭代器具有弱一致性,这意味着它们能够反映出集合在迭代器创建时或迭代过程中的状态。虽然迭代器中的元素可能不反映集合的所有最新变化,但它们确保了在没有锁的情况下也能安全地进行迭代。
在桶数组初始化和扩容时使用了延迟加载技术,这样可以减少在实际需要前就分配资源和加锁的情况。
这种设计通过限制锁的范围并且利用CAS等无锁操作,大大减少了锁竞争,使得ConcurrentHashMap成为一个适合高并发应用程序的高效线程安全集合。
本篇文章来源于微信公众号: 互联网面试小帮手
微信扫描下方的二维码阅读本文

Comments NOTHING