Java中的ConcurrentHashMap原理详解

ConcurrentHashMap介绍 ConcurrentHashMap从JDK1.5开始随java.util.concurrent包一起引入JDK中,主要为了解决HashMap线程不安全和HashTable效率不高的问题。 阅读详情
AI摘要

本文详细介绍了ConcurrentHashMap在Java中的重要性,尤其是在并发编程中的优势,以及其在不同JDK版本下的数据结构和底层原理,重点讲解了put()和get()方法的工作机制,以及1.8版本中采用的CAS、synchronized和红黑树等技术以提高并发性能和扩容效率。,

一、概述
ConcurrentHashMap是(java.util.concurrent包)的重要成员,它是HashMap的一个线程安全的、支持高效并发的版本,ConcurrentHashMap和HashMap一样,是一个存放键值对的容器。使用hash算法来获取值的地址,因此时间复杂度是O(1),查询非常快。

二、为什么要使用 ConcurrentHashMap?
1.在并发编程中,jdk1.7的情况下使用 HashMap 可能造成死循环,而jdk1.8 中有可能会造成数据丢失。

2.HashTable 虽然也是线程安全的,但是效率非常低下。

三、ConcurrentHashMap 结构
1.在jdk1.7版本
(1)ConcurrentHashMap的数据结构是由一个Segment数组和多个HashEntry组成。
(2)主要实现原理是实现了锁分离的思路,采用分段锁的机制,实现并发的更新操作。
(3)底层采用数组+链表的存储结构,包括两个核心静态内部类Segment和HashEntry。
(4)Segment数组的意义就是将一个大的table分割成多个小的table来进行加锁,也就是上面的提到 的锁分离技术。每一个Segment元素存储的是HashEntry数组+链表(若干个桶),这个和HashMap的数据存储结构一样。
(5)HashEntry用来封装映射表的键值对,每个桶是由若干个HashEntry对象链接起来的链表。

2.在jdk1.8后
(1)取消了Segment类,直接用table数组存储键值对。采用Node + CAS + Synchronized来保证并发安全。
(2)Node数据结构比较简单,就是一个链表,但是只允许对数据进行查找,不允许进行修改。
(3)当HashEntry对象组成的链表长度超过8时,或数组长度小于64 就会扩容,则链表转换为红黑树,提升性能。底层变更为数组+链表+红黑树。

四、ConcurrentHashMap底层原理
以jdk1.8为例:
1.Node节点数字用的是volatile修饰。

//ConcurrentHashMap使用volatile修饰节点数组,保证其可见性,禁止指令重排。
transient volatile Node<K,V>[] table;

3.ConcurrentHashMap的put()方法。

//put()方法直接调用putVal()方法
public V put(K key, V value) {
 return putVal(key, value, false);
}
//所以直接看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) {
            if (casTabAt(tab, i, null,
                         new Node<K,V>(hash, key, value, null)))
                break;                  
        }
        else if ((fh = f.hash) == MOVED)
            tab = helpTransfer(tab, f);
        else {
            V oldVal = null;
            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;
}

从上面看put方法并未用synchronized修饰。
其put大致过程如下:
(1)根据 key 计算出 hashcode,然后开始遍历 table;
(2)判断是否需要初始化;
(3)f 即为当前 key 定位出的 Node,如果为空表示当前位置可以写入数据,利用 CAS 尝试写入,失败则自旋保证成功。
(4)如果当前位置的 hashcode == MOVED == -1,则需要进行扩容。
(5)如果都不满足,则利用 synchronized 锁写入数据。
(6)如果数量大于 TREEIFY_THRESHOLD ,则要转换为红黑树。

4.ConcurrentHashMap的get()方法

//ConcurrentHashMap的get()方法是不加锁的,方法内部也没加锁。
public V get(Object key)

那么为什么ConcurrentHashMap的get()方法是不加锁的,因为table有volatile关键字修饰,保证每次获取值都是最新的。

get方法源码:

public V get(Object key) {
    Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
    int h = spread(key.hashCode());
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (e = tabAt(tab, (n - 1) & h)) != null) {
        // 判断头节点是否就是我们需要的节点
        if ((eh = e.hash) == h) {
            if ((ek = e.key) == key || (ek != null && key.equals(ek)))
                return e.val;
        }
        // 如果头节点的 hash 小于 0,说明 正在扩容,或者该位置是红黑树
        else if (eh < 0)
            // 参考 ForwardingNode.find(int h, Object k) 和 TreeBin.find(int h, Object k)
            return (p = e.find(h, key)) != null ? p.val : null;

        // 遍历链表
        while ((e = e.next) != null) {
            if (e.hash == h &&
                ((ek = e.key) == key || (ek != null && key.equals(ek))))
                return e.val;
        }
    }
    return null;
}

get过程:
(1)首先根据key计算出来的 hashcode 寻址,如果就在桶上那么直接返回值,
(2)如果是红黑树那就按照树的方式获取值,
(3)都不满足那就按照链表的方式遍历获取值。

总结:
1.高并发性能:ConcurrentHashMap JDK1.8 在 1.7 的数据结构上做了大的改动,采用了CAS+synchronized+Node+红黑树实现,结合了原子操作的优势,同时也保持了锁的简洁性。
2.扩容机制:ConcurrentHashMap 在扩容时采用了更为高效的复制方式,通过创建新的数组并逐步替换旧的数组来完成,这样可以减少对于锁的竞争。

ConcurrentHashMap:使用方法和底层原理详解 ConcurrentHashMap是Java集合框架中的一个线程安全的哈希表实现,它提供了比Hashtable和同步的HashMap更高的并发性能。ConcurrentHashMap在JDK 1.5中引入,通过采用分段锁的方式实现了高效的并发访问。它可以在保证线程安全的同时,提供较高的并发性能,因此是多线程环境下使用频率较高的数据结构之一。 阅读详情

相关推荐

Java中的ConcurrentHashMap的使用与原理

ConcurrentHashMap 是 Java 中为‌高并发场景‌设计的线程安全哈希表实现,位于 java.util.concurrent 包。它解决了 Hashtable 和 Collections.synchronizedMap() 的锁粒度过大导致的性能问题,在保证线程安全的同时显著提升并发吞吐量。ConcurrentHashMap 是 Java 中线程安全的哈希表实现,其核心原理在于通过精细化锁机制(分段锁或 CAS + synchronized)实现高并发性能。

pan_junbiao的博客 1112

ConcurrentHashMap详解

文章目录spread()方法putVal()方法initTable()方法tryPresize()tableSizeFor()resizeStamp()transfer()总结compute()addCount() 计算成员数量sumCount()fullAddCount()资源锁sizeCtl其它节点类型forwarding节点ReservationNode节点 spread()方法 作用:用于获取槽位值 static final int spread(int h) { //h为k

唐芬奇的博客 8万+

Java 中 ConcurrentHashMap 的工作原理

从 JDK 1.7 开始,主要采用了分段锁(Segment)的技术,在 JDK 1.8 及之后的版本中进行了优化,移除了分段锁的概念,转而使用更细粒度的锁机制,基于 CAS(Compare And Swap)和 synchronized 块来实现更高的并发性能。

Z1019879912的博客 997

Map 综述(三):彻头彻尾理解 ConcurrentHashMap

ConcurrentHashMap是J.U.C的重要成员,它是HashMap的一个线程安全的版本。在默认理想状态下,ConcurrentHashMap可以支持16个线程执行并发写操作及任意数量线程的读操作。本文将结合Java内存模型和JDK源代码,剖析其高并发的具体实现机制,包括在JDK中的定义和结构、并发存取、重哈希和跨段操作,并着重剖析了ConcurrentHashMap读操作不需要加锁的内在奥秘和原理。

Rico's Blogs 14万+

Java ConcurrentHashMap 高并发安全实现原理解析

首先需要明确的是,C13Map的读操作一般是不加锁的(TreeBin的读解锁除外),而读操作与写操作有可能并行;可以保证的是,因为C13Map的写操作都要获取bin头部的syncronized互斥锁,能保证最多只有一个线程在做更新,这其实是一个单线程写、多线程读的并发安全性的问题。C13Map的get方法int n, eh;K ek;//执行扰动函数= null) {= null?= null) {

2401_84253850的博客 1207

【java】并发容器之ConcurrentHashMap(JDK 1.8版本)

在使用HashMap时在多线程情况下扩容会出现CPU接近100%的情况,因为hashmap并不是线程安全的,通常我们可以使用在java体系中古老的hashtable类,该类基本上所有的方法都采用synchronized进行线程安全的控制,可想而知,在高并发的情况下,每次只有一个线程能够获取对象监视器锁,这样的并发性能的确不令人满意。另外一种方式通过Collections的将hashmap包装成一个线程安全的map。

九师兄 4318

Java8 ConcurrentHashMap 原理详解

Java8 ConcurrentHashMap 原理详解 目录Java8 ConcurrentHashMap 原理详解1:简介1.1 ConcurrentHashMap 结构1.2 约定1.3 重要成员变量简介2 put 方法2.1 put方法简要流程2.2 put 方法源码2.3 总结点数如何记录3 扩容4 get 方法5 delete方法 1:简介 1.1 ConcurrentHashMap 结构 ​ JDK 1.8 的 HashMap 的数据结构如上图所示,当链表节点较少时仍然是以链表存在,当链表节

cx897459376的博客 2651

Java并发编程之ConcurrentHashMap的原理和使用

‌ConcurrentHashMap(CHM)‌ 是 Java 并发包中实现线程安全的哈希表,其核心设计目标是‌在高并发场景下兼顾性能与线程安全‌。与 HashTable 的全表锁不同,CHM 通过‌分段锁(JDK7)‌和‌CAS+synchronized细粒度锁(JDK8+)‌ 实现高效并发控制,解决了传统同步容器的性能瓶颈‌。锁粒度细化‌:从分段锁(JDK7)到桶锁(JDK8)的演进‌;无锁化设计‌:CAS操作降低线程阻塞概率‌;数据结构优化‌:红黑树替代长链表提升查询效率‌;

天天新知 1313

【Java 基础】ConcurrentHashMap 底层原理

ConcurrentHashMap 是线程安全的哈希表,它是 Java 并发包中提供的一种高效的并发 Map 实现。ConcurrentHashMap 底层采用了分段锁的机制,不同的段(Segment)可以被不同的线程同时访问,从而提高了并发性能。

激流丶的博客 5642

ECB oracle——attack

基于ECB oracle的攻击方案前言什么是ECB方案步骤1.确定块大小和明文大小2.暴力破解求明文脚本代码 前言 参考文章:Breaking ECB by prepending your own message. 环境地址:ECB oracle 什么是ECB 分组密码中AES的一种模式(其他还有:CBC、ECB、CTR、OCF、CFB)如图每一块加密是分开的。 方案步骤 1.确定块大小和明文大小 先用一个字符"a"去encrypt返回32个字节的数据(64位16进制)依次用"aa",“aaa”…去en

weixin_44617902的博客 1311

硬件学习笔记(器件篇)—— 磁珠与磁环

因为有这些差别,所有经常会让人们觉得他们并不是一类器件,但实际上他们的原理是一样的。我们使用磁珠是为了利用损耗消耗高频能量,所以如果我们理解了磁珠是如何消耗高频能量的,我们就能理解磁珠的工作原理。

y_u_yu_yu_的博客 1万+

Java8 ConcurrentHashMap实现原理

Java8ConcurrentHashMap实现原理,基本和Java8的HashMap相同,不同于他的是前者保证了线程的安全性,和Java7的ConcurrentHashMap区别在于,两者保证线程安全性的机理不同,Java7中采用了“分段”锁的概念,每一个分段都有一把锁,锁内存储的着数据,锁的个数在初始化之后不能扩容,Java7 ConcurrentHashMap原理 传送门 但是Java8摒弃了这种设计理念,和Java8一样采用数组+链表+红黑树的概念来实现,那么要保证是线程安全(synchron..

hu10131013的博客 15万+

41-dify案例分享-基于database插件实现Text2sql的数据库查询图表工作流

本文介绍基于 Text2SQL 的 Dify 工作流实现过程。先阐述 Text2SQL 概念、步骤及应用领域。接着说明工作流制作,包括用 chatflow 加开场白、获取系统时间,配置 LLM 生成 SQL,用 SQL Execute 工具查询,经 LLM 汇总结果,代码生成 ECharts 数据,条件分支判断图表类型输出。最后介绍验证测试、提供体验地址,还提及借助工具生成建表和初始化数据 SQL 语句。

个人技术 1万+

Java8之ConcurrentHashMap实现原理

在java8之前ConcurrentHashMap是使用分段锁来实现并发的,数据结构为hashmap(数组加链表)的基础上再套一层segment数组,锁加在segment元素上。java8实现了粒度更细的加锁,去掉了segment数组,直接使用synchronized锁住hash后得到的数组下标位置中的第一个元素 ,如下图,这样加锁比segment加锁能支持更高的并发量。 另外,在java8...

ddxd0406的博客 9589

自定义载板RK3588HDMI输入配置完整解决方案

✅ HDMI输入信号检测✅ 自动时序识别(支持1080p@60Hz等标准格式)✅ V4L2视频设备接口✅ 实时视频流捕获✅ H.264编码录制✅ HDCP内容保护支持问题根因识别:发现了hdmim1模块资源冲突的本质问题硬件设计理解:明确了Orange Pi 5 Plus的真实硬件设计意图功能实现:成功启用HDMI输入功能,替代了无法使用的HDMI1输出完整验证:建立了从硬件检测到视频录制的完整测试流程。

梁俊星的博客 1271

lammps后处理:堆垛层错和孪晶的数量统计

python调用ovito包可以免费实现ovito pro版本的功能,不需要安装ovito pro软件,如统计位错线长度、CNA结构、键角分布等,如需深入学习,可考虑报名python ovito后处理课程,或lammps一对一课程。lammps金属变形模拟中,可能会产生堆垛层错或者孪晶等结构,但是ovito免费版本无法统计这些结构的数量。使用python和ovito软件包,则可以方便的统计出这些结构的数量。大家好,我是小马老师。

lammps_jiayou的博客 469

Java集合——ConcurrentHashMap(介绍、底层原理)

Java集合——ConcurrentHashMap(介绍、底层原理)

weixin_64451672的博客 1137

YOLOv11改进 | 网络结构代码逐行解析(一) | 项目目录构造分析

Hello,大家好这次给大家带来的不是改进,是整个YOLOv11项目的分析整个系列大概会更新5-7篇左右的文章从项目的目录到每一个功能代码的都会进行详细的讲解,下面开始进行YOLOv11逐行解析的第一篇——项目目录构造分析开头之前顺便给大家推荐一下我的专栏,本专栏更新上百余篇YOLOv11改进机制手把手教你添加到网络结构中,同时针对拿到模型不知道如何修改,不知道如何发表论文的读者进行针对性的文章介绍,本专栏质量分平均分96分,内容质量完全有所保证。

Snu77的博客 1万+

Java之ConcurrentHashMap线程安全原理

对于Java程序员来说,HashMap是他们用到最多的映射(键值对)数据结构,但是HashMap是线程不安全的,在多线程的环境下,建议使用线程安全的ConcurrentHashMap。本文将深入源码介绍一下ConcurrentHashMap如何确保线程安全。

Uncommon的博客 1300

华为od 面试八股文_测试/测试开发_05_含答案

华为od 面试八股文_测试/测试开发_05_含答案

MISAYAONE的博客 2558

YOLOv12改进前必看 - YAML模型配置文件详细解读(再也不用担心通道数不匹配) 附网络结构图

YOLOv12的模型配置文件在中,里面包含yolov12的以及的不同大小的模型,本文将详细介绍一下YOLOv12模型文件中各参数的含义。

Limiiiing的博客 3278

深入理解Java中的ConcurrentHashMap:原理与实践

本文深入剖析Java中的ConcurrentHashMap,探讨其实现原理与应用,以帮助读者理解并发编程挑战并掌握高效线程安全的HashMap实践。

陆业聪 2128

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

Java8 中ConcurrentHashMap工作原理详解

Java 8中的通过采用细粒度的锁策略、CAS操作、红黑树和逆序迁移等机制实现了高并发的读写操作。这种设计使得在并发场景下具有较高的性能,并且减少了线程间的竞争和阻塞。因此,在需要高并发的场景下,是一个非常好的选择。

u012808915的博客 623

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

ConcurrentHashMap的演进:从Java 8之前到Java 17的实现原理深度剖析

Java 8之前的通过分段锁的设计实现了高并发性能。它将哈希表划分为多个段,并使用细粒度的锁来控制对每个段的访问。这种设计大大减少了锁的竞争,提高了并发性能。然而,随着Java版本的迭代和硬件性能的提升,分段锁的设计逐渐暴露出一些问题,如内存占用较大、扩容操作复杂等。Java 8中的通过采用CAS操作结合synchronized同步块的并发控制策略以及优化后的数据结构和哈希算法等技术手段实现了高并发性能下的线程安全访问。与之前的版本相比,它在简化数据结构、提高空间利用率和降低锁竞争等方面取得了显著的进步。

码到三十五 1736

Java 中 HashMap 和 ConcurrentHashMap 核心原理解析

在现代软件开发中,数据的存储和检索是核心操作之一。高效的数据结构可以大大提高程序的运行效率,减少内存占用,提升用户体验。Java 作为一种广泛应用的编程语言,提供了多种数据结构来满足不同的需求,其中 HashMap 和 ConcurrentHashMap 是常用的键值对存储结构。本文深入探讨了 Java 中 HashMap 和 ConcurrentHashMap 的核心原理。HashMap 采用数组 + 链表 / 红黑树的存储结构,通过哈希函数将键映射到数组的索引位置,当负载因子达到一定值时会进行扩容操作。

持续分享有趣的技术干货 955
上一篇: Springcloud中的@RefreshScope详解
下一篇: Springboot如何实现接口重试
夜空下的星
博客等级 码龄9年 281粉丝 98原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

夜空下的星

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值