面试题答案
一键面试JDK 1.7 中 ConcurrentHashMap 的实现原理
- 数据结构:采用分段数组(Segment)和哈希表(HashEntry)。Segment 继承自 ReentrantLock,每个 Segment 内部是一个 HashEntry 数组。HashEntry 包含 key、value、next 指针等。当需要存储数据时,先根据 key 的哈希值定位到对应的 Segment,再在该 Segment 内的 HashEntry 数组中进行插入。
- 锁机制:使用分段锁,每个 Segment 都有独立的锁。当对某个 Segment 进行写操作时,只需要获取该 Segment 的锁,其他 Segment 仍可并发访问,从而提高了并发性能。读操作一般不需要加锁,因为 HashEntry 的 value 和 next 字段使用 volatile 修饰,保证了可见性。
- 线程安全保证:通过分段锁和 volatile 修饰符保证线程安全。写操作加锁,防止多线程同时修改同一 Segment 的数据;读操作通过 volatile 保证能读取到最新的数据。
JDK 1.8 中 ConcurrentHashMap 的实现原理
- 数据结构:摒弃了 Segment 结构,采用数组 + 链表 + 红黑树的数据结构。数组中的每个元素可以是一个链表,当链表长度超过阈值(默认为 8)时,会将链表转换为红黑树以提高查询效率。
- 锁机制:使用 CAS(Compare and Swap)和 synchronized 关键字。在数组元素为空时,使用 CAS 操作进行插入;当数组元素不为空时,对该元素(链表头或红黑树的根节点)使用 synchronized 加锁进行操作。
- 线程安全保证:通过 CAS 操作保证原子性插入,synchronized 关键字保证对同一元素的操作互斥,从而保证线程安全。同时,利用 volatile 保证数组和节点的可见性。
两个版本之间的改进点
- 数据结构优化:JDK 1.8 采用更高效的数组 + 链表 + 红黑树结构,相比 JDK 1.7 的分段数组 + 哈希表,在链表长度较长时查询性能有显著提升。
- 锁粒度更细:JDK 1.7 的分段锁是基于 Segment,粒度较大;JDK 1.8 直接对数组元素加锁,锁粒度更细,并发性能进一步提高。
- 锁机制优化:JDK 1.8 减少了 ReentrantLock 的使用,更多地使用 CAS 和 synchronized,在保证线程安全的同时,提高了性能。
- 内存占用优化:JDK 1.8 去除了 Segment 结构,减少了内存占用。