java并发-ConcurrentHashMap 在Java7 和 8 的区别

news2024/9/22 11:41:08

文章目录

  • 1.Java 7 版本的 ConcurrentHashMap
  • 2.Java 8 版本的 ConcurrentHashMap
  • 3.分析 Java 8 版本的 ConcurrentHashMap 的重要源码
    • 3.1.Node 节点
    • 3.2.put 方法源码分析
    • 3.3.get 方法源码分析
  • 4.对比 Java7 和 Java8 的异同和优缺点
    • 4.1.并发度
    • 4.2.保证并发安全的原理
    • 4.3.遇到 Hash 碰撞
    • 4.4.查询时间复杂度


1.Java 7 版本的 ConcurrentHashMap

我们首先来看一下 Java 7 版本中的 ConcurrentHashMap 的结构示意图:
在这里插入图片描述

从图中我们可以看出,在 ConcurrentHashMap 内部进行了 Segment 分段, Segment 继承了ReentrantLock,可以理解为一把锁,各个 Segment 之间都是相互独立上锁的,互不影响。相比于之前的Hashtable每次操作都需要把整个对象锁住而言,大大提高了并发效率。因为它的锁与锁之间是独立的,而不是整个对象只有一把锁。
每个 Segment 的底层数据结构与 HashMap 类似,仍然是数组和链表组成的拉链法结构。默认有 0~15共 16Segment,所以最多可以同时支持 16 个线程并发操作(操作分别分布在不同的 Segment 上)。 16 这个默认值可以在初始化的时候设置为其他值,但是一旦确认初始化以后,是不可以扩容 的。

2.Java 8 版本的 ConcurrentHashMap

在 Java 8 中的示意图:

在这里插入图片描述

图中的节点有三种类型。

  • 第一种是最简单的,空着的位置代表当前还没有元素来填充。
  • 第二种就是和 HashMap 非常类似的拉链法结构,在每一个槽中会首先填入第一个节点,但是后续 如果计算出相同的 Hash 值,就用链表的形式往后进行延伸。
  • 第三种结构就是红黑树结构,这是 Java 7 的 ConcurrentHashMap 中所没有的结构。

当第二种情况的链表长度大于某一个阈值(默认为 8),且同时满足一定的容量要求的时候,ConcurrentHashMap 便会把这个链表从链表的形式转化为红黑树的形式,目的是进一步提高它的查找性能。
红黑树是每个节点都带有颜色属性的二叉查找树,颜色为红色黑色,是一种平衡二叉查找树,查找效率高,会自动平衡,防止极端不平衡从而影响查找效率的情况发生。由于自平衡的特点,即左右子树高度几乎一致,所以其查找性能近似于二分查找,时间复杂度是O(log(n))级别;反观链表,它的时间复杂度就不一样了,如果发生了最坏的情况,可能需要遍历整个链表才能找到目标元素,时间复杂度为 O(n),远远大于红黑树的 O(log(n)),尤其是在节点越来越多的情况下,O(log(n))体现出的优势会更加明显。
红黑树的一些其他特点:

  • 每个节点要么是红色,要么是黑色,但根节点永远是黑色的。
  • 红色节点不能连续,也就是说,红色节点的子和父都不能是红色的。
  • 从任一节点到其每个叶子节点的路径都包含相同数量的黑色节点。

正是由于这些规则和要求的限制,红黑树保证了较高的查找效率,好处就是避免在极端的情况下冲突链表变得很长,在查询的时 候,效率会非常慢。而红黑树具有自平衡的特点,所以,即便是极端情况下,也可以保证查询效率在O(log(n))

3.分析 Java 8 版本的 ConcurrentHashMap 的重要源码

由于 Java 7 版本已经过时了,所以我们把重点放在 Java 8 版本的源码分析上。

3.1.Node 节点

我们看看最基础的内部存储结构 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里面是key-value的形式,并且把 valuevolatile 修饰,以便保证可见性,同时内部还有一个指向下一个节点的 next 指针,方便产生链表结构。
下面我们看两个最重要、最核心的方法。

3.2.put 方法源码分析

put 方法的核心是 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;
}

以下是代码的主要功能和步骤解释:

  1. 参数检查: 首先,代码检查传入的键和值是否为null,如果是,则抛出NullPointerException
  2. 计算哈希: 使用键的哈希码,并通过spread方法对哈希码进行扩散,以减少哈希冲突的可能性。
  3. 循环处理: 进入一个无限循环,用于处理可能的哈希冲突和表的初始化。
  4. 初始化表: 如果哈希表为null或长度为0,则通过调用initTable方法初始化哈希表。
  5. 处理空桶: 如果计算得到的桶是空的,则尝试使用CAS(Compare and Swap)原子操作添加新的Node节点到该桶中,如果成功则退出循环。
  6. 处理转移中的桶: 如果当前桶的状态是MOVED(正在进行桶的迁移操作),则调用helpTransfer方法协助进行桶的迁移。
  7. 处理非空桶: 如果桶非空,说明存在哈希冲突,根据桶的类型(链表或树)采取不同的处理方式。
  • 如果桶中是链表(fh >= 0),则使用同步块在链表中查找键值对。如果找到了相同的键,更新其值,否则将新的Node节点添加到链表中。
  • 如果桶中是树(f instanceof TreeBin),则调用putTreeVal方法在树中插入键值对。
  1. 更新计数: 最后,根据实际添加的节点数更新哈希表中元素的计数。
  2. 返回值: 如果在添加操作中替换了已存在的值,返回被替换的旧值,否则返回null。

3.3.get 方法源码分析

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; // 返回找到的值
        }
    }

    // 未找到匹配的键,返回null
    return null;
}

  1. 计算哈希: 计算传入键的哈希值,并通过spread方法进行扩散。
  2. 检查表和桶: 检查哈希表是否已经初始化,以及哈希桶中是否存在节点。
  3. 处理第一个节点: 检查第一个节点是否匹配,如果匹配则返回对应的值。
  4. 处理树结构: 如果哈希桶中是树结构,则调用find方法查找匹配的键值对。
  5. 遍历链表: 在链表中遍历查找匹配的键值对,如果找到则返回对应的值。
  6. 未找到匹配: 如果遍历完仍未找到匹配的键,则返回null。

4.对比 Java7 和 Java8 的异同和优缺点

数据结构
Java 7 采用 Segment 分段锁来实现,而 Java 8 中的 ConcurrentHashMap 使用数组 + 链表 + 红黑树。

在这里插入图片描述

4.1.并发度

Java 7 中,每个 Segment 独立加锁,最大并发个数就是 Segment 的个数,默认是 16
Java 8 中,锁粒度更细,理想情况下 table 数组元素的个数(也就是数组长度)就是其支持并
发的最大个数,并发度比之前有提高。

4.2.保证并发安全的原理

Java 7 采用 Segment 分段锁来保证安全,而 Segment 是继承自 ReentrantLock
Java 8 中放弃了 Segment 的设计,采用 Node+CAS+synchronized 保证线程安全。

4.3.遇到 Hash 碰撞

Java 7 在 Hash 冲突时,会使用拉链法,也就是链表的形式。
Java 8 先使用拉链法,在链表长度超过一定阈值时,将链表转换为红黑树,来提高查找效率。

4.4.查询时间复杂度

Java 7 遍历链表的时间复杂度是O(n),n 为链表长度。
Java 8 如果变成遍历红黑树,那么时间复杂度降低为 O(log(n)),n 为树的节点个数。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1325328.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Spring Boot构建项目常用注解

忙着去耍帅&#xff0c;后期补充完整.....................................

文件上传 [SWPUCTF 2021 新生赛]easyupload1.0

打开题目 上传文件格式为jpg类型的一句话木马上去 木马内容为 bp抓包把文件后缀改为php 可以看到上传成功且给了我们上传路径&#xff0c;访问一下 访问成功 我们用蚁剑连接一下&#xff0c;但是找到的是假的flag 那我们用hackbar&#xff08;也可以用bp&#xff09; 执行命…

法线贴图实现衣服上皱褶特效

在线工具推荐&#xff1a; 3D数字孪生场景编辑器 - GLTF/GLB材质纹理编辑器 - 3D模型在线转换 - Three.js AI自动纹理开发包 - YOLO 虚幻合成数据生成器 - 三维模型预览图生成器 - 3D模型语义搜索引擎 法线贴图在3D建模中扮演着重要的角色&#xff0c;它通过模拟表面的微…

S7-200PLC与MCGS昆仑通泰触摸屏进行串口通信的具体方法示例

S7-200PLC与MCGS昆仑通泰触摸屏进行串口通信的具体方法示例 PLC:CPU 224XP CN 触摸屏:TPC1570GI 通信电缆的连接: 触摸屏7+对应PLC 3+,触摸屏8-对应PLC 8- 触摸屏和PLC的硬件连接如下图所示: PLC一侧的组态设置: 第一步:(在编程软件系统块中打开通讯端口设置) 第二步:…

《数据分析-JiMuReport》积木报表详细入门教程

积木报表详细入门教程 一、JimuReport部署入门介绍 积木报表可以通过源码部署、SpringBoot集成、Docker部署以及各种成熟框架部署&#xff0c;具体可查看积木官方文档 当前采用源码部署&#xff0c;首先下载Jimureport-example-1.5.6 1 jimureport-example目录查看 使用ID…

H5聊天系统聊天网站源码 群聊源码 无限建群创群

H5聊天系统聊天网站源码 群聊源码 无限建群创群 1.支持自助建群 管理群 修改群资料 2.支持自动登录 登陆成功可自助修改资料 3.后台可查看群组聊天消息记录 4.支持表情 动态表情 图片发布 5.支持消息语音提醒 测试环境&#xff1a;NginxMySQL5.6PHP5.6 1.将压缩包解压到…

阿赵UE学习笔记——3、常用界面窗口

阿赵UE学习笔记目录 大家好&#xff0c;我是阿赵。继续学习虚幻引擎&#xff0c;这次介绍的是编辑器的常用界面窗口。 一、视口 这个视口的概念&#xff0c;可以体现出UE对于多屏幕同时显示是多么的友好。我们开发游戏的时候&#xff0c;一般都会同一台电脑用2个或者以上显示器…

【【迭代七次的CORDIC算法-Verilog实现】】

迭代七次的CORDIC算法-Verilog实现求解正弦余弦函数 COEDIC.v module CORDIC #(parameter DATA_WIDTH 4d8 , // we set data widthparameter PIPELINE 4d8)(input clk ,input …

命令执行 [SWPUCTF 2021 新生赛]babyrce

打开题目 我们看到题目说cookie值admin等于1时&#xff0c;才能包含文件 bp修改一下得到 访问rasalghul.php&#xff0c;得到 题目说如果我们get传入一个url且不为空值&#xff0c;就将我们get姿势传入的url的值赋值给ip 然后用正则过滤了 / /&#xff0c;如果ip的值没有 / …

Android 13 - Media框架(24)- OMXNodeInstance(一)

为了了解 ACodec 是如何与 OpenMAX 组件进行 buffer 流转的&#xff0c;我们有必要先来学习 OMXNodeInstance&#xff0c;在前面的章节中&#xff0c;我们已经了解了 media.codec 进程包含的内容&#xff0c;以及 OpenMAX 框架中的一些内容。这一节我们将来学习 OMXNode 与 med…

X 进制减法问题

思路如下&#xff1a; 代码如下&#xff1a; #include<iostream> using namespace std; const int N 1e5 5; const long long MOD 1000000007; //定义了常量 N 和 MOD。N 是数组的最大长度&#xff0c;MOD 是取模运算的模数。int numsA[N], numsB[N]; //声明了两个数…

IDEA创建springboot工程

选择spring boot的版本和依赖 finish创建完成 删除无用的文件

Android 自动化测试——Monkey测试

Android自带了很多方便的测试工具和方法&#xff0c;包括我们常用的单元测试、Robotium测试、Monkey测试、MonkeyRunner测试、senevent模拟等。这些方法对于我们编写高质量的APP十分有用。也可以提前暴露我们程序的隐藏问题。今天给大家讲一下Monkey测试&#xff0c;Monkey测试…

页面菜单,通过get请求一个url后,跳转另外一个页面,+丢失问题

业务场景描述&#xff1a; 在A系统&#xff0c;菜单点击跳B系统这个操作。 A系统菜单是get请求到B系统的一个缓冲页面&#xff0c;然后这个缓冲页面获取到url中的accessToken后&#xff0c;在这个页面中通过post请求后端接口。 问题描述&#xff1a; 当accessToken中包含了…

Stable Diffusion Windows 部署简单认知

写在前面 偶然看到&#xff0c;简单了解博文为 SD 部署&#xff0c;以及简单使用&#xff0c;部署过程遇到问题解决理解不足小伙伴帮忙指正 对每个人而言&#xff0c;真正的职责只有一个&#xff1a;找到自我。然后在心中坚守其一生&#xff0c;全心全意&#xff0c;永不停息。…

adam优化器和动量

原始的SGD 加上动量&#xff08;惯性&#xff0c;每一次更新根据前面所有结果&#xff0c;使结果更快收敛&#xff09; AdaGrad 与SGD的核心区别在于计算更新步长时&#xff0c;增加了分母&#xff1a;梯度平方累积和的平方根。此项能够累积各个参数 的历史梯度平方&#xf…

Vue 2.5 入门学习记录

Vue 2.5 入门学习记录 1. 基础知识Vue 是什么Vue引入方式Vue特点Vue实例中的数据事件方法Vue中的属性绑定和双向绑定Vue中的v-if、v-show、v-fortoDoList制作局部组件&全局组件 2. vue-cli工程3. 工程案例实践使用vue-cli实现todoList及删除某个元素全局样式与局部样式 4. …

2024年camtasia怎么导出mp4

Camtasia 2024是一款屏幕录制和视频剪辑软件&#xff0c;教授课程&#xff0c;培训他人&#xff0c;以更快的速度和更吸引人的方式进行沟通和屏幕分享。使您在Windows和Mac上进行录屏和剪辑创作专业外观的视频变得更为简单。 Camtasia Studio 2023 win-安装包&#xff1a;https…

智能优化算法应用:基于蜜獾算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于蜜獾算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于蜜獾算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.蜜獾算法4.实验参数设定5.算法结果6.参考文献7.MA…