Interview AiBox logo

Interview AiBox 实时 AI 助手,让你自信应答每一场面试

download免费下载
4local_fire_department26 次面试更新于 2025-08-23account_tree思维导图

ConcurrentHashMap的实现原理是什么?

lightbulb

题型摘要

ConcurrentHashMap是Java并发包中的线程安全HashMap实现,旨在提供高并发性和高性能。JDK 1.7及之前采用分段锁(Segment)设计,每个Segment守护一部分数据,不同Segment的操作可并发执行。JDK 1.8及之后改为使用CAS操作和synchronized关键字,锁粒度更细,只锁住需要修改的桶,进一步提高了并发性。ConcurrentHashMap的读操作通常不需要加锁,写操作只锁定必要的部分,支持多线程并发扩容,整体性能远超Hashtable和Collections.synchronizedMap。其实现体现了无锁算法、细粒度锁、并发扩容等并发编程思想,是高性能并发编程的重要组件。

ConcurrentHashMap的实现原理

1. 基本概念和设计目标

ConcurrentHashMap是Java并发包(java.util.concurrent)中的一个线程安全的HashMap实现。它的设计目标是在保证线程安全的同时,提供比Hashtable和Collections.synchronizedMap更高的并发性能。

设计目标

  • 高并发性:允许多个线程同时读和一定程度的写操作
  • 线程安全:保证多线程环境下数据的一致性
  • 高性能:尽可能减少锁竞争,提高并发访问效率

2. JDK 1.7及之前的实现(分段锁)

在JDK 1.7及之前的版本中,ConcurrentHashMap采用**分段锁(Segment)**的设计来实现并发控制。

内部结构

ConcurrentHashMap内部由一个Segment数组组成,每个Segment又包含一个HashEntry数组。Segment是一种可重入锁(ReentrantLock),每个Segment守护着它所包含的HashEntry数组中的元素。

--- title: ConcurrentHashMap 1.7 内部结构 --- graph TD A["ConcurrentHashMap"] --> B["Segment数组"] B --> C["Segment 0"] B --> D["Segment 1"] B --> E["Segment 2"] B --> F["Segment N"] C --> G["HashEntry数组"] D --> H["HashEntry数组"] E --> I["HashEntry数组"] F --> J["HashEntry数组"] G --> K["HashEntry 0"] G --> L["HashEntry 1"] G --> M["HashEntry N"]

工作原理

  1. 数据定位

    • 首先通过key的hash值确定在哪个Segment中
    • 然后在该Segment中通过再次hash确定具体的HashEntry位置
  2. 并发控制

    • 写操作需要获取对应Segment的锁
    • 读操作不需要加锁(通过volatile保证可见性)
    • 不同Segment的写操作可以并发进行
  3. 容量计算

    • 整个ConcurrentHashMap的容量是所有Segment容量的总和
    • 每个Segment的容量默认为16,负载因子为0.75

代码示例

// JDK 1.7 ConcurrentHashMap的put方法简化版
public V put(K key, V value) {
    Segment<K,V> s;
    // 计算key的hash值,并找到对应的Segment
    int hash = hash(key.hashCode());
    int j = (hash >>> segmentShift) & segmentMask;
    if ((s = (Segment<K,V>)UNSAFE.getObject          // nonvolatile; recheck
         (segments, (j << SSHIFT) + SBASE)) == null) //  in ensureSegment
        s = ensureSegment(j);
    // 调用Segment的put方法
    return s.put(key, hash, value, false);
}

// Segment的put方法简化版
final V put(K key, int hash, V value, boolean onlyIfAbsent) {
    // 尝试获取锁,如果失败则自旋获取
    HashEntry<K,V> node = tryLock() ? null :
        scanAndLockForPut(key, hash, value);
    V oldValue;
    try {
        HashEntry<K,V>[] tab = table;
        int index = (tab.length - 1) & hash;
        HashEntry<K,V> first = entryAt(tab, index);
        for (HashEntry<K,V> e = first;;) {
            if (e != null) {
                K k;
                if ((k = e.key) == key ||
                    (e.hash == hash && key.equals(k))) {
                    oldValue = e.value;
                    if (!onlyIfAbsent) {
                        e.value = value;
                        ++modCount;
                    }
                    break;
                }
                e = e.next;
            }
            else {
                if (node != null)
                    node.setNext(first);
                else
                    node = new HashEntry<K,V>(hash, key, value, first);
                int c = count + 1;
                if (c > threshold && tab.length < MAXIMUM_CAPACITY)
                    rehash(node);
                else
                    setEntryAt(tab, index, node);
                ++modCount;
                count = c;
                oldValue = null;
                break;
            }
        }
    } finally {
        unlock();
    }
    return oldValue;
}

优缺点

优点

  • 锁粒度细,并发度高
  • 在多线程环境下读写性能较好

缺点

  • 内存占用较高(每个Segment都维护自己的HashEntry数组)
  • 在某些场景下,并发度可能不够高(Segment数量固定)

3. JDK 1.8及之后的实现(CAS + synchronized)

从JDK 1.8开始,ConcurrentHashMap的实现发生了重大变化,放弃了分段锁的设计,转而采用CAS(Compare And Swap)操作和synchronized关键字来实现并发控制。

内部结构

JDK 1.8的ConcurrentHashMap内部结构与HashMap类似,由一个Node数组组成,每个Node可能是一个链表或红黑树的头节点。

--- title: ConcurrentHashMap 1.8 内部结构 --- graph TD A["ConcurrentHashMap"] --> B["Node数组"] B --> C["Node 0"] B --> D["Node 1"] B --> E["Node 2"] B --> F["Node N"] C --> G["链表或红黑树"] D --> H["链表或红黑树"] E --> I["链表或红黑树"] F --> J["链表或红黑树"] G --> K["Node"] G --> L["Node"] G --> M["Node"]

工作原理

  1. CAS操作

    • 在无竞争的情况下,使用CAS操作来更新节点的值
    • CAS是一种无锁算法,通过比较并交换来保证原子性
  2. synchronized锁

    • 在有竞争的情况下,使用synchronized锁住对应的桶(Node数组中的某个位置)
    • 锁的粒度更细,只锁住需要修改的桶,而不是整个Segment
  3. 关键优化

    • 扩容优化:支持多线程并发扩容
    • 树化优化:当链表长度超过8时,转换为红黑树,提高查询效率
    • 计数优化:使用CounterCell来记录元素数量,减少竞争

代码示例

// JDK 1.8 ConcurrentHashMap的putVal方法简化版
final V putVal(K key, V value, boolean onlyIfAbsent) {
    if (key == null || value == null) throw new NullPointerException();
    int hash = spread(key.hashCode());
    int binCount = 0;
    for (Node<K,V>[] tab = table;;) {
        Node<K,V> f; int n, i, fh;
        if (tab == null || (n = tab.length) == 0)
            tab = initTable();
        else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
            // 如果桶为空,使用CAS操作添加新节点
            if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null)))
                break;                   // no lock when adding to empty bin
        }
        else if ((fh = f.hash) == MOVED)
            tab = helpTransfer(tab, f);
        else {
            V oldVal = null;
            // 使用synchronized锁住桶的头节点
            synchronized (f) {
                if (tabAt(tab, i) == f) {
                    if (fh >= 0) {
                        binCount = 1;
                        for (Node<K,V> e = f;; ++binCount) {
                            K ek;
                            if (e.hash == hash &&
                                ((ek = e.key) == key ||
                                 (ek != null && key.equals(ek)))) {
                                oldVal = e.val;
                                if (!onlyIfAbsent)
                                    e.val = value;
                                break;
                            }
                            Node<K,V> pred = e;
                            if ((e = e.next) == null) {
                                pred.next = new Node<K,V>(hash, key,
                                                          value, null);
                                break;
                            }
                        }
                    }
                    else if (f instanceof TreeBin) {
                        Node<K,V> p;
                        binCount = 2;
                        if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                              value)) != null) {
                            oldVal = p.val;
                            if (!onlyIfAbsent)
                                p.val = value;
                        }
                    }
                }
            }
            if (binCount != 0) {
                if (binCount >= TREEIFY_THRESHOLD)
                    treeifyBin(tab, i);
                if (oldVal != null)
                    return oldVal;
                break;
            }
        }
    }
    addCount(1L, binCount);
    return null;
}

优缺点

优点

  • 内存占用更少(不需要维护多个Segment)
  • 锁粒度更细,并发度更高
  • 在高并发场景下性能更好

缺点

  • 实现更复杂,理解难度更高
  • 在某些低并发场景下,性能可能不如JDK 1.7的实现

4. 关键操作的实现原理

get操作

--- title: ConcurrentHashMap get操作流程 --- sequenceDiagram participant Thread as 线程 participant CHM as ConcurrentHashMap Thread->>CHM: get(key) CHM->>CHM: 计算hash值 CHM->>CHM: 定位到对应的桶 CHM->>CHM: 遍历链表或红黑树 CHM->>CHM: 返回找到的值 CHM-->>Thread: 返回结果
  1. JDK 1.7

    • 不加锁,通过volatile保证可见性
    • 首先定位到Segment,然后在Segment中查找对应的HashEntry
  2. JDK 1.8

    • 不加锁,通过volatile保证可见性
    • 直接通过hash值定位到对应的桶,然后遍历链表或红黑树

put操作

--- title: ConcurrentHashMap put操作流程 --- sequenceDiagram participant Thread as 线程 participant CHM as ConcurrentHashMap Thread->>CHM: put(key, value) CHM->>CHM: 计算hash值 CHM->>CHM: 定位到对应的桶 alt 桶为空 CHM->>CHM: 使用CAS操作添加新节点 else 桶不为空 CHM->>CHM: 使用synchronized锁住桶的头节点 CHM->>CHM: 遍历链表或红黑树 CHM->>CHM: 更新或添加节点 CHM->>CHM: 释放锁 end CHM->>CHM: 检查是否需要扩容或树化 CHM-->>Thread: 返回结果
  1. JDK 1.7

    • 首先定位到Segment,然后获取Segment的锁
    • 在Segment中执行put操作,可能触发rehash
    • 释放锁
  2. JDK 1.8

    • 首先定位到桶
    • 如果桶为空,使用CAS操作添加新节点
    • 如果桶不为空,使用synchronized锁住桶的头节点
    • 执行put操作,可能触发树化或扩容
    • 释放锁

size操作

  1. JDK 1.7

    • 遍历所有Segment,累加它们的count值
    • 在累加过程中,可能尝试获取Segment的锁(如果连续两次获取的值不一致)
  2. JDK 1.8

    • 使用CounterCell数组来记录元素数量
    • 在多线程环境下,不同线程可能更新不同的CounterCell
    • 计算size时,累加所有CounterCell的值

扩容操作

--- title: ConcurrentHashMap 扩容流程 --- flowchart TD A["开始扩容"] --> B["创建新的Node数组"] B --> C["将旧数组的元素迁移到新数组"] C --> D["是否有其他线程协助扩容?"] D -->|是| E["分配任务给其他线程"] D -->|否| F["当前线程完成所有迁移"] E --> G["多线程并发迁移"] G --> H["等待所有迁移完成"] F --> H H --> I["更新table引用"] I --> J["扩容完成"]
  1. JDK 1.7

    • 每个Segment独立扩容,不影响其他Segment
    • 扩容时获取Segment的锁,阻塞其他线程对该Segment的操作
  2. JDK 1.8

    • 支持多线程并发扩容
    • 扩容时,将数组分成多个部分,不同线程可以负责迁移不同的部分
    • 迁移过程中,读操作可以并发进行,写操作可能会协助扩容

5. 与其他线程安全Map的对比

--- title: 线程安全Map对比 --- graph TD A["线程安全Map对比"] --> B["Hashtable"] A --> C["Collections.synchronizedMap"] A --> D["ConcurrentHashMap"] B --> E["锁机制"] E --> F["对象锁(synchronized)"] B --> G["性能特点"] G --> H["写操作阻塞整个表"] G --> I["并发度低"] C --> J["锁机制"] J --> K["对象锁(synchronized)"] C --> L["性能特点"] L --> M["写操作阻塞整个表"] L --> N["并发度低"] D --> O["锁机制"] O --> P["JDK 1.7: 分段锁"] O --> Q["JDK 1.8: CAS + synchronized"] D --> R["性能特点"] R --> S["锁粒度细"] R --> T["并发度高"] R --> U["读操作无锁"]

Hashtable

  • 锁机制:使用synchronized关键字锁住整个表
  • 性能特点
    • 任何写操作都会阻塞整个表
    • 读操作也需要加锁
    • 并发度低,性能较差

Collections.synchronizedMap

  • 锁机制:使用synchronized关键字锁住整个Map对象
  • 性能特点
    • 任何写操作都会阻塞整个Map
    • 读操作也需要加锁
    • 并发度低,性能较差
    • 迭代时需要手动加锁

ConcurrentHashMap

  • 锁机制
    • JDK 1.7:分段锁(Segment)
    • JDK 1.8:CAS + synchronized
  • 性能特点
    • 锁粒度细,只锁住需要修改的部分
    • 读操作不需要加锁(通过volatile保证可见性)
    • 并发度高,性能较好
    • 支持复合操作的原子性(如putIfAbsent)

6. 总结

ConcurrentHashMap是Java并发包中一个重要的线程安全Map实现,它的设计目标是提供高并发性和高性能。

  • JDK 1.7及之前:采用分段锁(Segment)的设计,每个Segment是一个独立的哈希表,拥有自己的锁。这种设计允许多个线程同时访问不同的Segment,提高了并发性。

  • JDK 1.8及之后:放弃了分段锁的设计,转而采用CAS操作和synchronized关键字。锁的粒度更细,只锁住需要修改的桶,进一步提高了并发性。

ConcurrentHashMap的实现原理体现了Java并发编程的一些重要思想,如无锁算法、细粒度锁、并发扩容等。理解ConcurrentHashMap的实现原理,对于编写高性能的并发程序非常有帮助。

参考资料

  1. Java ConcurrentHashMap官方文档
  2. Java并发编程实战
  3. The Java® Virtual Machine Specification
  4. Java并发源码分析:ConcurrentHashMap
account_tree

思维导图

Interview AiBox logo

Interview AiBox — 面试搭档

不只是准备,更是实时陪练

Interview AiBox 在面试过程中提供实时屏幕提示、AI 模拟面试和智能复盘,让你每一次回答都更有信心。

AI 助读

一键发送到常用 AI

ConcurrentHashMap是Java并发包中的线程安全HashMap实现,旨在提供高并发性和高性能。JDK 1.7及之前采用分段锁(Segment)设计,每个Segment守护一部分数据,不同Segment的操作可并发执行。JDK 1.8及之后改为使用CAS操作和synchronized关键字,锁粒度更细,只锁住需要修改的桶,进一步提高了并发性。ConcurrentHashMap的读操作通常不需要加锁,写操作只锁定必要的部分,支持多线程并发扩容,整体性能远超Hashtable和Collections.synchronizedMap。其实现体现了无锁算法、细粒度锁、并发扩容等并发编程思想,是高性能并发编程的重要组件。

智能总结

深度解读

考点定位

思路启发

auto_awesome

相关题目

在软件开发中,如何设计有效的测试用例?

设计有效测试用例需遵循明确性、完整性、独立性等原则,运用等价类划分、边界值分析等黑盒测试技术和语句覆盖、分支覆盖等白盒测试技术。针对单元测试、集成测试、系统测试和验收测试等不同级别,采用相应的设计策略和方法。测试用例应包含完整的文档结构,使用专业工具进行管理,并基于风险分析确定优先级。最佳实践包括测试用例复用、自动化测试和定期评审,避免过度依赖脚本、忽视负面测试等常见误区。

arrow_forward

请详细说明ArrayList和LinkedList的区别,包括它们的底层实现、性能特点和使用场景。

ArrayList和LinkedList是Java中两种常用的List实现,它们在底层实现、性能特点和使用场景上有显著差异。ArrayList基于动态数组实现,具有O(1)的随机访问性能,但插入/删除操作需要移动元素,时间复杂度为O(n);LinkedList基于双向链表实现,随机访问性能为O(n),但插入/删除操作只需修改指针,时间复杂度为O(1)。ArrayList适合读多写少、需要频繁随机访问的场景;LinkedList适合写多读少、需要频繁在头部或中间插入/删除的场景,同时它还实现了Deque接口,可作为队列或双端队列使用。在实际开发中,ArrayList的使用频率更高,因为大多数场景下随机访问的需求更常见,且内存效率更高。

arrow_forward

HashMap的底层原理是什么?它是线程安全的吗?在多线程环境下会遇到什么问题?如果要保证线程安全应该使用什么?ConcurrentHashMap是怎么保证线程安全的?请详细说明。

HashMap基于数组+链表/红黑树实现,通过哈希函数计算元素位置,使用链地址法解决哈希冲突。HashMap是非线程安全的,多线程环境下可能导致死循环、数据覆盖等问题。线程安全的替代方案包括Hashtable、Collections.synchronizedMap()和ConcurrentHashMap。ConcurrentHashMap在JDK 1.7采用分段锁实现,JDK 1.8改用CAS+synchronized,锁粒度更细,并发性能更好。

arrow_forward

Java中的集合框架(Collection & Map)有哪些主要接口和实现类?

Java集合框架主要分为Collection和Map两大体系。Collection体系包括List(有序可重复,如ArrayList、LinkedList)、Set(无序不可重复,如HashSet、TreeSet)和Queue(队列,如PriorityQueue、ArrayDeque)。Map体系存储键值对,主要实现类有HashMap、LinkedHashMap、TreeMap、Hashtable和ConcurrentHashMap等。不同集合类在底层结构、有序性、线程安全、时间复杂度等方面有不同特性,应根据具体需求选择合适的实现类。

arrow_forward

请详细介绍一下你参与过的项目,包括项目背景、你的职责以及使用的技术栈。

面试者需要清晰介绍参与过的项目,包括项目背景、个人职责、使用的技术栈、遇到的挑战及解决方案,以及项目成果和个人收获。重点突出自己在项目中的具体贡献、技术选型的思考过程、解决问题的思路以及从中获得的成长。回答应结构清晰,重点突出,体现技术深度和解决问题的能力。

arrow_forward

阅读状态

阅读时长

10 分钟

阅读进度

4%

章节:23 · 已读:0

当前章节: 1. 基本概念和设计目标

最近更新:2025-08-23

本页目录

Interview AiBox logo

Interview AiBox

AI 面试实时助手

面试中屏幕实时显示参考回答,帮你打磨表达。

免费下载download

分享题目

复制链接,或一键分享到常用平台

外部分享