【Java基础】ConcurrentHashMap原理分析及应用

Java ConcurrentHashMap 的并发统计功能实现 在多线程编程中,我们经常需要对数据进行统计操作。普通的 HashMap 不是线程安全的,在多线程环境下使用会出现数据不一致的问题。而 ConcurrentHashMap 是 Java 提供的线程安全的哈希表,它可以在多线程环境下高效地进行并发操作。本文的目的就是详细介绍如何利用 ConcurrentHashMap 实现并发统计功能,范围涵盖其核心概念、原理、代码实现以及实际应用等方面。 阅读详情

引言

本篇博客是基于之前的HashMap开展的,读者若是对HashMap不甚了解的话,可以参看之前的博客。
【Java基础】HashMap底层实现原理 附源码分析及面试题,让你深入Java集合
简单来说,ConcurrentHashMap是HashMap的并发版本,适合在并发环境下使用。
另外,本文关于ConcurrentHashMap的源码是基于JDK1.8的。

原理分析

ConcurrentHashMap原理:JDK1.7中,ConcurrentHashMap 采用了分段锁技术,其中 Segment是ConcurrentHashMap的内部类, 继承于 ReentrantLock。不会像 Hashtable 那样不管是 put 还是 get 操作都需要做同步处理,理论上 ConcurrentHashMap 支持 CurrencyLevel (Segment 数组数量)的线程并发。每当一个线程占用锁访问一个 Segment 时,不会影响到其他的 Segment。如下图所示:

在这里插入图片描述
但是从JDK1.8开始,ConcurrentHashMap就抛弃了原有的分段锁,而是改用CAS+synchronized来保证并发安全性。同时在JDK1.8中,也弃用了HashEntry,改用了Node(但功能相同):

static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        volatile V val;
        volatile Node<K,V> next;

        Node(int hash, K key, V val, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.val = val;
            this.next = next;
        }

        public final K getKey()       { return key; }
        public final V getValue()     { return val; }
        public final int hashCode()   { return key.hashCode() ^ val.hashCode(); }
        public final String toString(){ return key + "=" + val; }
        public final V setValue(V value) {
            throw new UnsupportedOperationException();
        }
        ……
  }

不难发现,val 和next 都用了 volatile 修饰,保证了并发条件下的可见性。

put方法

先上源码:

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;
        /* 注释1 */
        for (Node<K,V>[] tab = table;;) {
            Node<K,V> f; int n, i, fh;
            /* 注释2 */
            if (tab == null || (n = tab.length) == 0)
                tab = initTable();
                /* 注释3 */
            else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
                if (casTabAt(tab, i, null,
                             new Node<K,V>(hash, key, value, null)))
                    break;                   // no lock when adding to empty bin
            }
            /* 注释4 */
            else if ((fh = f.hash) == MOVED)
                tab = helpTransfer(tab, f);
            else {
                V oldVal = null;
                /* 注释5 */
                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) {
                /* 注释6 */
                    if (binCount >= TREEIFY_THRESHOLD)
                        treeifyBin(tab, i);
                    if (oldVal != null)
                        return oldVal;
                    break;
                }
            }
        }
        addCount(1L, binCount);
        return null;
    }
  1. 根据 key 计算出 hashCode ;
  2. 判断是否需要进行初始化;
  3. f 即为当前 key 定位出的 Node,如果为空表示当前位置可以写入数据,利用 CAS 尝试写入,失败则自旋保证成功;
  4. 如果当前位置的 hashCode == MOVED (值为-1),则需要进行扩容;
  5. 如果都不满足,则利用 synchronized 锁写入数据;
  6. 如果数量大于 TREEIFY_THRESHOLD (值为8)则要转换为红黑树。

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;
            }
            else if (eh < 0)
                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;
    }

根据计算出来的 hashCode 寻址,如果就在桶上那么直接返回值,如果是红黑树那就按照树的方式获取值,否则就按照链表的方式遍历获取值。

参考阅读

Java并发包concurrent—ConcurrentHashMap
自旋锁与互斥锁的对比、手工实现自旋锁

飞腾D2000 BIOS编译避坑指南:PCIE拆分、S3电源与过温保护配置详解 本文详细解析飞腾D2000 BIOS编译中的关键配置技巧,包括PCIE拆分模式优化、S3电源管理适配及过温保护设置。通过实战案例和参数调优建议,帮助开发者规避常见陷阱,充分发挥国产高性能计算平台的硬件潜力。特别针对BIOS编译过程中的隐蔽问题提供解决方案。 阅读详情

相关推荐

数字音频接口之I2S总线协议详解

1、SDATA,也就是串行数据信号,使用二进制传输数字信号。请注意数据的传输形式为补码的方式2、例如音频的采样率是8K,一s内存采样8K个点,一个点包含一个左声道,一个右声道,所以LRCK是8K,BCLK是2×采样频率×采样位数3、同时双向(TX和RX)音频流:串行数据同步移位到时钟信号SCK和LRCK。在SCK的下降沿将TX数据写入SDOUT引脚,并在SCK的上升沿从SDIN引脚读取RX数据。始终首先发送最高有效位(MSB);TX和RX均可在主模式和从模式下使用。

卓学课堂 3万+

Java集合---ConcurrentHashMap原理分析

一、背景: 线程不安全的HashMap     因为多线程环境下,使用Hashmap进行put操作会引起死循环,导致CPU利用率接近100%,所以在并发情况下不能使用HashMap。   效率低下的HashTable容器      HashTable容器使用synchronized来保证线程安全,但在线程竞争激烈的情况下HashTable的效率非常低下。因为当一个线程访问HashTabl...

weixin_34128237的博客 680

RapidScada在windows下的安装(详细)

1.下载地址:RapidScada官网 2.版本说明 有Linux版本和Windows版本,看自己的需求下载,推荐下载最新稳定版本。 3.系统要求 4.安装前的准备(windows10) 首先打开Windows功能: 先关闭 Microsoft .NET Framework3.5的 Windows Communication Foundation 子组件。如下图: Microsoft .NET Framework配置按下图勾选: iis安装的功能: 5.安装 解压..

m0_51275803的博客 4263

ConcurrentHashMap源码分析(黑马面试题笔记)

了解ConcurrentHashMap容器的初始化

Guo_Liwei的博客 1220

DRM驱动代码分析:展频 跳频

DRM驱动代码分析:展频 跳频

yuuu_cheer的博客 2679

Word毕业论文排版:图片和表格题注的终极指南(含自动编号技巧)

本文是Word毕业论文排版中图片和表格题注的终极指南,详细讲解了如何利用题注功能实现自动编号,告别繁琐易错的手动操作。核心内容包括创建定制标签、配置带章节的编号格式、插入交叉引用以及一键更新全文编号的技巧,旨在帮助学生构建自动化文档体系,提升排版效率和论文严谨性。

weixin_29211309的博客 1095

ConcurrentHashMap原理分析

一、出现背景      1、线程不安全的HashMap      因为多线程环境下,使用Hashmap进行put操作会引起死循环,导致CPU利用率接近100%,所以在并发情况下不能使用HashMap。 2、效率低下的HashTable容器      HashTable容器使用synchronized来保证线程安全,但在线程竞争激烈的情况下HashTable的效率非常低下。因为当一个线程访问...

weixin_33910385的博客 438

别再只飞不记了!用CsvView分析你的大疆飞行日志,优化航拍与排查炸机原因

本文详细介绍了如何利用CsvView工具分析大疆无人机的飞行日志,帮助用户优化航拍效率和飞行安全。通过解读飞行日志中的关键数据,如电池性能、飞行轨迹等,用户可以及时发现潜在问题并改进飞行习惯。文章还提供了事故排查方法和进阶分析技巧,适合无人机爱好者和专业飞手。

weixin_42535461的博客 549

ConcurrentHashMap原理深度解析

在Java中,ConcurrentHashMap(简称CHM)是一个线程安全的哈希表实现,它支持高并发场景下的读写操作。与HashMap和Hashtable不同,ConcurrentHashMap在设计和实现上做了大量的优化,以便于在保证线程安全的前提下提供更高的吞吐量和更低的延迟。本文将详细解析ConcurrentHashMap的原理、线程安全性的实现方式、扩容机制以及为什么选择这样的扩容策略,并结合源代码进行说明。本文基于jdk1.8的源码作为示例。

Jz_Stu的博客 2920

Qt Tab Widget 实战:如何打造一个可动态增删的标签页管理器(附完整代码)

本文深入探讨了使用Qt框架中的Tab Widget控件构建动态标签页管理器的实战方法。通过分析动态增删的核心挑战,提供了完整的C++代码示例,涵盖信号槽机制、页面生命周期管理、上下文菜单实现以及状态持久化等关键功能,帮助开发者打造高效、用户友好的桌面应用界面。

bb456的博客 1043

TWINCAT3保姆级教程:手把手教你给Ecat从站烧录EEPROM(含XML配置避坑指南)

本文提供TWINCAT3环境下Ecat从站EEPROM烧录的完整教程,涵盖XML配置避坑指南。详细讲解环境准备、设备扫描、XML文件配置、EEPROM烧录流程及故障排查,帮助工程师高效解决设备识别问题,提升产线调试效率。

weixin_30693683的博客 516

ConcurrentHashMap的原理分析

JDK1.7: 底层数据结构:数组(sgement)、数组(HashEntry)、链表(HashEntry节点) 两个主要的内部类: class Segment内部类,继承ReentrantLock,有一个HashEntry数组,用来存储链表头结点 class HashEntry 定义的节点,里面存储的数据和下一个节点 主要方法: get()方法: 1、第一次哈希 找到 对应的Segment段,调用Segment中的get方法 2、再次哈希找到对应的链表, 3、最后在链表中查找。 put()方法: 1、首

致力于不留技术债务cuizb.top 974

Python爬虫 汽车之家 车型销量--参数分析与实现

【代码】Python爬虫 汽车之家 车型销量--参数分析与实现。

2201_76125393的博客 850

Java7-8下的HashMap与ConcurrentHashMap

本文章来源于:https://github.com/Zeb-D/my-review ,请star 强力支持,你的支持,就是我的动力。 [TOC] Java7/8 下 HashMap 与 ConcurrentHashMap 网上关于 HashMap 和 ConcurrentHashMap 的文章确实不少,不过有很多没那么全面,自己也写一篇,把细节说清楚说透,尤其像 Java8 中的 Concurr...

在此立一个IT职业上的flag:https://github.com/Zeb-D/my-review 1万+

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

阿里十年架构师,教你深度分析ConcurrentHashMap原理分析

ConcurrentHashMap的初步使用及场景 CHM的使用 ConcurrentHashMap是J.U.C包里面提供的一个线程安全并且高效的HashMap,所以ConcurrentHashMap在并发编程的场景中使用的频率比较高,那么这一节课我们就从ConcurrentHashMap的使用上以及源码层面来分析ConcurrentHashMap到底是如何实现安全性的 ap...

qq_39662660的博客 972

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

ConcurrentHashMap内部原理分析

集合是编程中最常用的数据结构。而谈到并发,几乎总是离不开集合这类高级数据结构的支持。比如两个线程需要同时访问一个中间临界区(Queue),比如常会用缓存作为外部文件的副本(HashMap)。这篇文章主要分析jdk1.5的3种并发集合类型(concurrent,copyonright,queue)中的ConcurrentHashMap,让我们从原理上细致的了解它们,能够让我们在深度项目开发中获益非浅

iTuring_Zhou的专栏 526

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

java ConcurrentHashMap原理分析

空间阿斯顿和发卡

u012760183的专栏 539

HashMap、ConcurrentHashMap原理分析

集合(Collection)是编程中常用的数据结构,而并发也是服务器端编程常用的技术之一,并发总是离不开集合这类高级数据结构的支持。比如两个线程需要同时访问一个中间临界区(Queue),比如常会用缓存作为外部文件的副本(HashMap)。而Map这种以键值对为元素的数据结构也是集合中最常用到的。Map家族中的三大类:HashMap、HashTable、ConcurrentHashMap。前者非线程...

weixin_34292287的博客 214

三十九、并发编程之ConcurrentHashMap源码解析

一、ConcurrentHashMap实现原理 众所周知,哈希表是中非常高效,复杂度为O(1)的数据结构,在Java开发中,我们最常见到最频繁使用的就是HashMap和HashTable,但是在线程竞争激烈的并发场景中使用都不够合理。 1.HashMap HashMap是线程不安全的,在并发环境下,可能会形成环状链表(扩容时可能造成),导致get操作时,cpu空转,所以,在并发环境中使用HashM...

白夜行悟空 390

hashMap & linkedHashMap & concurrentHashMap 原理----legend050709

(1)hashMap (2)linkedHashMap ---------------- (1)hashMap (1.1)hash Hash 就是把任意长度的输入(又叫做预映射, pre-image),通过哈希算法,变换成固定长度的输出(通常是整型),该输出就是哈希值。这种转换是一种 压缩映射 ,也就是说,散列值的空间通常远小于输入的空间。不同的输入可能会散列成相同的输出,从而不可能...

legend050709的专栏 470

关于HashMap、Hashtable、ConcurrentHashMap等区别、ConcurrentHashMap实现线程安全的知识点总结

目录 一、ArrayList、LinkedList、Vector的区别 二、HashMap和Hashtable的区别 三、HashMap和ConcurrentHashMap的区别 四、HashMap和LinkedHashMap的区别 五、HashMap是非线程安全的原因及实现线程安全的方法 六、ConcurrentHashMap实现线程安全的原理 一、ArrayList、Linked...

Xx丶浅时光的博客 849

Java编程中的并发容器ConcurrentHashMap设计与应用详解

随着Java版本的演进,尤其是到了Java 8,ConcurrentHashMap的内部实现发生了重大变化,放弃了分段锁,转而采用更为优化的CAS(Compare-And-Swap)操作、synchronized锁以及更精细化的锁粒度(锁住单个桶的头节点),并引入了红黑树来解决哈希冲突严重时的性能问题,使其在并发性能和空间效率上达到了新的高度。:这是一个非常强大的方法。在Java 8的实现中,get操作通常是无锁的,因为它读取的是volatile类型的value,保证了可见性,这使得读操作的性能非常高。

RussellFF的博客 891

并发容器(Map、List、Set)实战及其原理分析

Java的集合容器框架中,主要有四大类别:List、Set、Queue、Map,大家熟知的这些集合类ArrayList、LinkedList、HashMap这些容器都是非线程安全的。所以,Java先提供了同步容器供用户使用。同步容器可以简单地理解为通过synchronized来实现同步的容器,比如Vector、Hashtable以及SynchronizedList等容器。这样做的代价是削弱了并发性,当多个线程共同竞争容器级的锁时,吞吐量就会降低。因此为了解决同步容器的性能问题,所以才有了并发容器。

weixin_43164559的博客 1301

深入剖析 ConcurrentHashMap:Java 并发编程的基石

ConcurrentHashMap是Java中支持高并发的哈希表实现。Java7采用分段锁(Segment)机制,通过16个Segment实现并发控制,每个Segment内部维护链表结构。Java8进行了重大改进:引入红黑树优化查询性能,采用CAS操作减少锁竞争,并取消Segment直接使用Node数组。它提供put、get、computeIfAbsent等线程安全方法,适用于缓存系统、计数器等并发场景。随着Java版本迭代,ConcurrentHashMap不断优化以提高并发性能和稳定性,是多线程环境下高

2402_85030292的博客 960
上一篇: 【Session】关于在用户退出登录的时候清除相关session信息
下一篇: 【并发编程】基础系列(一):线程安全性
少侠露飞
博客等级 码龄9年 214粉丝 117原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值