ConcurrentHashMap 原理(1)之内部类HashEntry 、Segment

本文详细分析了ConcurrentHashMap的内部结构,包括HashEntry和Segment的作用及工作方式,阐述了如何通过分离链接法处理碰撞,并介绍了ConcurrentHashMap在并发环境下的高效实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

ConcurrentHashMap 的结构分析

ConcurrentHashMap 类中包含两个静态内部类 HashEntry 和 Segment。HashEntry 用来封装映射表的键 / 值对;Segment 用来充当锁的角色,每个 Segment 对象守护整个散列映射表的若干个桶。每个桶是由若干个 HashEntry对象链接起来的链表。一个 ConcurrentHashMap 实例中包含由若干个 Segment 对象组成的数组。

HashEntry 类

HashEntry 用来封装散列映射表中的键值对。在 HashEntry 类中,key,hash和 next 域都被声明为 final 型,value 域被声明为 volatile 型。


在 ConcurrentHashMap 中,在散列时如果产生“碰撞”,将采用“分离链接法”来处理“碰撞”:把“碰撞”的 HashEntry 对象链接成一个链表。由于 HashEntry 的 next 域为 final 型,所以新节点只能在链表的表头处插入。 下图是在一个空桶中依次插入 A,B,C 三个 HashEntry 对象后的结构图:

图 1. 插入三个节点后桶的结构示意图:


注意:由于只能在表头插入,所以链表中节点的顺序和插入的顺序相反。

Segment 类

Segment 类继承于 ReentrantLock 类,从而使得 Segment 对象能充当锁的角色。每个 Segment 对象用来守护其(成员对象 table 中)包含的若干个桶。

table 是一个由 HashEntry 对象组成的数组。table 数组的每一个数组成员就是散列映射表的一个桶。

count 变量是一个计数器,它表示每个 Segment 对象管理的 table 数组(若干个 HashEntry 组成的链表)包含的 HashEntry 对象的个数。每一个 Segment 对象都有一个 count 对象来表示本 Segment 中包含的 HashEntry 对象的总数。注意,之所以在每个 Segment 对象中包含一个计数器,而不是在 ConcurrentHashMap 中使用全局的计数器,是为了避免出现“热点域”而影响 ConcurrentHashMap 的并发性。

清单 2.Segment 类的定义


下图是依次插入 ABC 三个HashEntry 节点后,Segment 的结构示意图。

图 2. 插入三个节点后 Segment 的结构示意图:



### Java 中 ConcurrentHashMap原理详解 #### 1. 概述 `ConcurrentHashMap` 是 Java 并发包中提供的一种线程安全的哈希表实现。它支持高并发环境下的读写操作,性能优于传统的 `Hashtable` 和使用 `synchronized` 包装的 `HashMap`[^2]。 #### 2. 数据结构与存储机制 在 JDK 1.7 和 JDK 1.8 中,`ConcurrentHashMap` 的实现有显著差异。 ##### 2.1 JDK 1.7 版本 - **数据结构**:由一个 `Segment` 数组和多个 `HashEntry` 组成。每个 `Segment` 相当于一个小的哈希表,内部维护了一个 `HashEntry` 数组[^3]。 - **锁分离**:`Segment` 数组的作用是将整个哈希表分割成多个小的区域,每个区域独立加锁,从而实现了锁分离的机制。这种设计减少了锁的竞争,提高了并发性能[^3]。 - **核心内部类**: - `Segment`:用于实现分段锁,类似于缩小版的哈希表。 - `HashEntry`:封装了键值对,并通过链表形式链接多个冲突的元素。 ```java static final class HashEntry<K, V> { final int hash; final K key; volatile V value; HashEntry<K, V> next; HashEntry(int hash, K key, V value, HashEntry<K, V> next) { this.hash = hash; this.key = key; this.value = value; this.next = next; } } ``` ##### 2.2 JDK 1.8 版本 - **数据结构**:JDK 1.8 对 `ConcurrentHashMap` 进行了优化,摒弃了 `Segment` 的概念,转而采用一种基于 CAS(Compare-And-Swap)操作的无锁算法[^4]。 - **数组+链表+红黑树**:底层仍然使用数组作为主存储结构,但在处理哈希冲突时,采用了链表或红黑树来存储冲突的节点。当链表长度超过一定阈值(默认为 8)时,会将链表转换为红黑树以提高查找效率[^4]。 - **CAS 操作**:对于非写入操作(如 `get`),直接通过无锁的方式访问数据;而对于写入操作(如 `put`、`remove`),则通过 CAS 操作保证线程安全[^4]。 ```java static final class Node<K, V> implements Map.Entry<K, V> { final int hash; final K key; volatile V value; volatile Node<K, V> next; Node(int hash, K key, V value, Node<K, V> next) { this.hash = hash; this.key = key; this.value = value; this.next = next; } } ``` #### 3. 线程安全性 - **读操作**:`ConcurrentHashMap` 的读操作不需要加锁,因为它利用了volatile变量的特性来保证可见性。 - **写操作**:写操作通过 CAS 或者分段锁来确保线程安全。例如,在插入新元素时,首先尝试使用 CAS 更新桶中的引用;如果失败,则通过加锁的方式完成更新[^4]。 - **扩容机制**:在多线程环境下,`ConcurrentHashMap` 支持动态扩容。扩容过程中,旧的数据会被重新分配到新的桶中,同时保证了读写的正确性和高效性。 #### 4. 性能比较 与 `HashMap` 和 `Hashtable` 相比,`ConcurrentHashMap` 在多线程场景下具有更高的性能。这是因为: - `HashMap` 完全不支持线程安全,需要外部同步。 - `Hashtable` 虽然线程安全,但它的所有方法都被 `synchronized` 修饰,导致全局锁开销较大。 - `ConcurrentHashMap` 通过分段锁或无锁机制,显著降低了锁的粒度,提升了并发性能[^4]。 #### 5. 示例代码 以下是一个简单的 `ConcurrentHashMap` 使用示例: ```java import java.util.concurrent.ConcurrentHashMap; public class ConcurrentHashMapExample { public static void main(String[] args) { ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>(); map.put("key1", 1); map.put("key2", 2); System.out.println("Value for key1: " + map.get("key1")); // 输出 Value for key1: 1 System.out.println("Value for key2: " + map.get("key2")); // 输出 Value for key2: 2 } } ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值