当前位置: 首页 > article >正文

Java 并发集合:ConcurrentHashMap 深入解析

Java 并发集合:ConcurrentHashMap 深入解析

1. 概述

ConcurrentHashMap 是 Java 并发包(java.util.concurrent)中的线程安全 Map 实现,支持高效的并发读写操作,适用于高并发环境。

在 Java 7 和 Java 8 版本中,ConcurrentHashMap 采用了不同的底层实现方式,Java 7 采用分段锁(Segment),而 Java 8 进行了优化,使用 CAS(Compare-And-Swap)+ synchronized + Node 结构。

2. 底层数据结构

Java 7 中的 ConcurrentHashMap(Segment 分段锁机制)

  • 采用 Segment + HashEntry 结构。
  • Segment 继承 ReentrantLock,每个 Segment 维护多个 HashEntry
  • 通过分段锁机制提高并发性,每个 Segment 维护独立的 HashEntry
  • 读操作无锁,写操作使用 Segment 级别的锁。
    在这里插入图片描述
    Java 7 中 ConcurrentHashMap 的存储结构如上图,ConcurrnetHashMap 由很多个 Segment 组合,而每一个 Segment 是一个类似于 HashMap 的结构,所以每一个 HashMap 的内部可以进行扩容。但是 Segment 的个数一旦初始化就不能改变,默认 Segment 的个数是 16 个,你也可以认为 ConcurrentHashMap 默认支持最多 16 个线程并发。

Java 8 中的 ConcurrentHashMap(CAS + synchronized 机制)

  • 取消了 Segment,采用 数组 + 链表 + 红黑树 结构。
  • 使用 Node<K, V>[] table 作为基础数据结构,链表冲突超过阈值时转换为红黑树。
  • 采用 CAS + synchronized 保证线程安全,提高了性能。
    在这里插入图片描述
    Java8 的 ConcurrentHashMap 相对于 Java7 来说变化比较大,不再是之前的 Segment 数组 + HashEntry 数组 + 链表,而是 Node 数组 + 链表 / 红黑树。当冲突链表达到一定长度时,链表会转换成红黑树。

3. 实现原理

1. 读操作(get 方法)

  • 读取 table[i] 位置的 Node,如果 key 存在,直接返回。
  • 在无竞争的情况下,读操作完全无锁。

2. 写操作(put 方法)

  • 使用 CAS 操作插入数据,避免不必要的锁开销。
  • CAS 失败,使用 synchronized 进行加锁。
  • Node 链表长度超过 8 时,转换为 红黑树 提高查询效率。

3. 扩容机制(rehash 过程)

  • 采用 渐进式扩容,避免 HashMap 扩容时的阻塞问题。
  • 扩容时,采用 转移任务拆分 的方式,由多个线程共同完成。

4. 应用场景

ConcurrentHashMap 适用于高并发场景,如:

  • 线程安全的 缓存 组件。
  • 统计 业务(如用户请求次数统计)。
  • 配置存储(如存储系统配置,避免使用 Hashtable)。

5. 优缺点

优点

✅ 线程安全,支持高并发读写。
✅ 读操作无锁,提高性能。
✅ 写操作部分 CAS 无锁,提高吞吐量。
✅ 采用红黑树优化链表查询效率。

缺点

❌ 不能保证严格的顺序(如 TreeMap)。
❌ 不能存储 null key 或 null value。
❌ 扩容仍然是一个性能瓶颈(尽管已优化)。

6. 替代方案

  • Collections.synchronizedMap(new HashMap<>()):简单的同步 Map,性能较低。
  • ConcurrentSkipListMap:支持 有序Map,适用于排序需求场景。
  • ReadWriteLock + HashMap:适用于读多写少的场景。

7. 使用示例

import java.util.concurrent.*;

public class ConcurrentHashMapExample {
    public static void main(String[] args) {
        ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();

        // 插入元素
        map.put("apple", 10);
        map.put("banana", 20);

        // 并发读取
        System.out.println("Apple count: " + map.get("apple"));

        // 并发修改
        map.compute("apple", (key, value) -> (value == null) ? 1 : value + 1);
        System.out.println("Updated apple count: " + map.get("apple"));
    }
}

8. 总结

  • Java 8ConcurrentHashMap 采用 CAS + synchronized + 红黑树 提升性能。
  • 适用于高并发读写场景,如缓存、计数、共享数据存储。
  • 相比 HashtablesynchronizedMap,具有更高的吞吐量和并发能力
  • 需要避免 null 作为键值对,扩容仍需关注性能消耗

Java7 中 ConcurrentHashMap 使用的分段锁,也就是每一个 Segment 上同时只有一个线程可以操作,每一个 Segment 都是一个类似 HashMap 数组的结构,它可以扩容,它的冲突会转化为链表。但是 Segment 的个数一但初始化就不能改变。

Java8 中的 ConcurrentHashMap 使用的 Synchronized 锁加 CAS 的机制。结构也由 Java7 中的 Segment 数组 + HashEntry 数组 + 链表 进化成了 Node 数组 + 链表 / 红黑树,Node 是类似于一个 HashEntry 的结构。它的冲突再达到一定大小时会转化成红黑树,在冲突小于一定数量时又退回链表。

有些同学可能对 Synchronized 的性能存在疑问,其实 Synchronized 锁自从引入锁升级策略后,性能不再是问题,有兴趣的同学可以自己了解下 Synchronized 的锁升级。

在多线程环境下,建议优先选择 ConcurrentHashMap 来替代 HashMapHashtable,以提高性能和并发安全性。


http://www.kler.cn/a/593513.html

相关文章:

  • html5基于Canvas的经典打砖块游戏开发实践
  • 齿轮热处理学习笔记分享
  • 本地部署deepseek-r1建立向量知识库和知识库检索实践【代码】
  • 多模态大模型:将音频向量化
  • 再学:合约继承 、抽象合约 solidity接口、库、事件 合约重入攻击
  • 快速迭代:利用 nodemon 和其他工具实现 Express.js 热更新
  • Wi-Fi NAN 架构(Wi-Fi Aware Specification v4.0,第2章:2.3~2.6)
  • python|exm5-3re模块,正则表达式概念介绍|match()、search()、findall()、sub()、split()
  • LoRA中黑塞矩阵、Fisher信息矩阵是什么
  • python主成分分析法1
  • python函数的多种参数使用形式
  • vue+echarts实现饼图组件(实现左右联动并且数据量大时可滚动)
  • GenICam标准
  • 【C#语言】C#中的同步与异步编程:原理、示例与最佳实践
  • MySQL 基础学习文档
  • LeetCode-有效括号
  • 使用Java实现Oracle表结构转换为PostgreSQL的示例方案(AI)
  • 非对称加密算法及逆向数据分析研究
  • 前端样式库推广——TailwindCss
  • 【STM32单片机】#1初识STM32新建工程