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

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,以提高性能和并发安全性。

相关文章:

  • 「C++输入输出」笔记
  • 上取整,下取整,四舍五入
  • IC/ID卡的卡号的不同格式的转换
  • created在vue3 script setup中的写法
  • redis搭建一主一从+keepalived(虚拟IP)实现高可用
  • 【8】分块学习笔记
  • 修改War包文件
  • PTA C语言程序设计 第三章
  • linux Redhat9.5采用DNS主从实现跨网段解析
  • 批量删除 PPT 中的所有图片、某张指定图片或者所有二维码图片
  • 【Java】——方法的使用(从入门到进阶)
  • deepseek使用记录99——为何追问
  • nginx 配置ip黑白名单
  • Docker 内部通信(网络)
  • 基于MySQL的创建<resultMap>和查询条件<if test>
  • zookeepernacoskafka之间的联系
  • Linux系统上后门程序的原理细节,请仔细解释一下
  • 【GeeRPC】项目总结:使用 Golang 实现 RPC 框架
  • PostgreSQL:语言基础与数据库操作
  • Cloudlog系统接口 search_log未授权存在SQL注入漏洞代码审计(DVB-2025-8964)
  • 婚姻登记“全国通办”首日观察:数据多跑路,群众少跑腿
  • 中美经贸高层会谈在瑞士日内瓦开始举行
  • 图集︱“中国排面”威武亮相
  • 工行回应两售出金条发现疑似杂质:情况不属实,疑似杂质应为金条售出后的外部附着物
  • 碧桂园境外债务重组:相当于现有公众票据本金额逾50%的持有人已加入协议
  • 国家出口管制工作协调机制办公室部署开展打击战略矿产走私出口专项行动