ConcurrentHashMap
是Java中一种线程安全且高效的哈希表实现,它在Java 8之后的版本中采用了与早期版本不同的扩容机制。在Java 8及以后的版本中,ConcurrentHashMap
利用了分段锁(Segment,直到Java 8)和之后的CAS(Compare and Swap)操作以及节点的树化来实现高效的并发读写和扩容,下面主要介绍Java 8及之后版本的扩容机制。
Java 8及以后的扩容机制概述
在Java8中,ConcurrentHashMap
摒弃了分段锁的设计,转而采用更细粒度的锁,即每个桶(bin)上的链表头节点作为锁的粒度,并引入了红黑树来优化链表过长时的查找效率。其扩容过程主要包括以下几个步骤:
1.初始化新容量:当需要扩容时,会计算出新的容量,并创建一个新的、容量更大的table。扩容的触发条件通常是当元素数量超过当前容量与负载因子乘积时。
2.迁移节点:扩容的核心在于将旧table中的元素重新分配到新的table中。这个过程不是一次性完成的,而是随着每次对map的操作逐步进行。在每个桶上的链表头节点加锁的情况下,线程会遍历该链表上的所有节点,使用新的hash算法(考虑了新容量)重新计算它们在新table中的位置,并将它们移动过去。对于已经迁移到新table的节点,会设置一个 forwardingNode 标记,指示后续访问直接跳转到新table,避免重复迁移。
3.树化处理:在迁移过程中,如果发现链表长度超过了特定阈值(默认为8),会将链表转换成红黑树结构,以保持高效的查找性能。
- 更新大小阈值:扩容后,会根据新的容量和负载因子计算新的大小阈值,超过这个阈值才会再次触发扩容。
特点
- 并发进行:由于采用了细粒度的锁,多个线程可以同时进行不同桶的元素迁移工作,大大提高了扩容的并发性。
- 无锁CAS操作:在某些情况下,比如节点的简单插入或更新,会尽量使用无锁的CAS操作来减少锁的竞争,提高性能。
- 渐进式:扩容不是一次性完成的,而是随着并发操作逐步完成,这样减少了对正常操作的影响。
总结
Java 8及之后版本的ConcurrentHashMap
通过细粒度的锁、CAS操作、以及红黑树的引入,实现了高效且低阻塞的扩容机制,确保了即使在大量并发操作下也能保持良好的性能。这一设计体现了现代并发编程中追求的“无锁”或“少锁”的设计理念。
1.7版本
- 1.7版本的ConcurrentHashMap是基于Segment分段实现的
- 每个Segment相对于一个小型的HashMap
- 每个Segment内部会进行扩容,和HashMap的扩容逻辑类似 4.先生成新的数组,然后转移元素到新数组中
- 扩容的判断也是每个Segment内部单独判断的,判断是否超过阈值
1.8版本
- 1.8版本的ConcurrentHashMap不再基于Segment实现
- 当某个线程进行put时,如果发现ConcurrentHashMap正在进行扩容那么该线程一起进行扩容 3.如果某个线程put时,发现没有正在进行扩容,则将key-value添加到ConcurrentHashMap中,然后判断是否超过阈值,超过了则进行扩容
- ConcurrentHashMap是支持多个线程同时扩容的 5.扩容之前也先生成—个新的数组 6.在转移元素时,先将原数组分组,将每组分给不同的线程来进行元素的转移,每个线程负责一组或多组的元素转移工作