CurrentHashMap的底层原理

news2024/11/16 21:57:20

CurrentHashMap在jdk1.8之前使用的是分段锁,在jdk1.8中使用"CAS和synchronized"来保证线程安全。

jdk1.8之前的底层实现

CurrentHashMap在jdk1.8之前,通过Segment段来实现线程安全。

Segment 段

ConcurrentHashMap 和 HashMap 思路是差不多的,但是因为它支持并发操作,所以要复杂一些。在jdk1.8之前,整个 ConcurrentHashMap 由一个个 Segment 组成,Segment 是“部分”或 “分段”的意思,所以很多地方都会将其描述为分段锁。本文中我们就用“槽”来代表一个segment。Segment类是线程安全的(Segment类继承 ReentrantLock,具有加锁功能) ,通过继承ReentrantLock,使用ReentrantLock的lock方法来实现加锁。

static class Segment<K,V> extends ReentrantLock implements Serializable {
    
}

简单理解就是,ConcurrentHashMap 是一个 Segment 数组,Segment 通过继承ReentrantLock 来进行加锁,所以每次加锁的操作锁住的是一个 Segment,每个Segment 内部维护一个小的哈希表,并且每个Segment 有自己的锁。操作只需锁住相关的段,从而提高并发度,只要保证每个 Segment 是线程安全的,也就实现了全局的线程安全。

每个 Segment 内部维护一个与 Hashtable 类似的数据结构(HashEntry<K,V>[] 数组),通常是一个数组+链表(在 JDK 1.8 之前)。当发生哈希冲突时,元素会以链表的形式存储在数组的同一个位置。

当线程需要删除或修改 Segment 中的数据时,ConcurrentHashMap首先需要获取该 Segment 的锁,即调用lock方法,这样可以确保同一时间只有一个线程能够修改 Segment 中的数据,对该 Segment 的数据操作是线程安全的,防止数据不一致的情况发生。

并行度(默认16)

concurrencyLevel:并行度,并行级别,并发数,Segment 数,默认是 16。

实际上我们可以在构造 ConcurrentHashMap 时通过构造函数参数自定义并行度。 例如 new ConcurrentHashMap<>(initialCapacity, loadFactor, concurrencyLevel) 中的 concurrencyLevel 参数。 默认情况下我们可以说 ConcurrentHashMap 有 16 个 Segment,所以理论上ConcurrentHashMap 默认最多可以同时支持 16 个线程并发写。这个并行度可以在初始化的时候设置为其他值,但是一旦初始化以后,它是不可以扩容的。再具体到每个Segment内部,其实每个 Segment 很像HashMap,不过它要保证线程安全,所以处理起来要麻烦些。

如何加锁

在 JDK 1.8 之前的 ConcurrentHashMap 中,每个 Segment 继承了 ReentrantLock,通过 lock()unlock() 方法实现了对 Segment 的锁定和解锁。

static final class Segment<K,V> extends ReentrantLock implements Serializable {
    // 存储在本Segment中的哈希表
    transient volatile HashEntry<K,V>[] table;
    // 获取键对应的值
    V get(Object key, int hash) {
        if (count != 0) { // 读取volatile变量
            HashEntry<K,V> e = getFirst(hash);
            while (e != null) {
                if (e.hash == hash && key.equals(e.key)) {
                    return e.value;
                }
                e = e.next;
            }
        }
        return null;
    }
    // 插入键值对
    V put(K key, int hash, V value, boolean onlyIfAbsent) {
        lock(); // 锁定Segment
        try {
            HashEntry<K,V>[] tab = table;
            int index = (tab.length - 1) & hash;
            HashEntry<K,V> first = tab[index];
            for (HashEntry<K,V> e = first; e != null; e = e.next) {
                K k;
                if ((k = e.key) == key || (e.hash == hash && key.equals(k))) {
                    V oldValue = e.value;
                    if (!onlyIfAbsent) {
                        e.value = value;
                    }
                    return oldValue;
                }
            }
            // 将新值作为第一个条目添加
            tab[index] = new HashEntry<K,V>(hash, key, value, first);
            return null;
        } finally {
            unlock(); // 解锁Segment
        }
    }
}
jdk1.8的底层实现

在jdk1.8中,ConcurrentHashMap 采用“CAS+synchronized”的机制来保证线程安全

CAS的具体使用细节
  1. 某个桶(结点)为空时,使用CAS操作往桶(结点)中插入新值
  2. 当桶数组(table)还没有初始化时,会使用 CAS 操作来保证只有一个线程能够成功地初始化这个数组
synchronized的具体使用细节

当桶(结点)不为空,存在元素时,即桶(结点)为链表或者红黑树时,就会给桶加synchronized锁,保证同时只有一个线程来操作桶。这些操作包括添加、修改或者删除桶中的元素

put方法源码
/**
 * Maps the specified key to the specified value in this table.
 * Neither the key nor the value can be null.
 *
 * <p>The value can be retrieved by calling the {@code get} method
 * with a key that is equal to the original key.
 *
 * @param key key with which the specified value is to be associated
 * @param value value to be associated with the specified key
 * @return the previous value associated with {@code key}, or
 *         {@code null} if there was no mapping for {@code key}
 * @throws NullPointerException if the specified key or value is null
 */
public V put(K key, V value) {
    return putVal(key, value, false);
}

final V putVal(K key, V value, boolean onlyIfAbsent) {
    // 如果 key 或 value 为 null,则抛出空指针异常
    if (key == null || value == null) throw new NullPointerException();
    
    // 计算 key 的 hash 值,并通过 spread 函数进行高位混合,减少冲突
    int hash = spread(key.hashCode());
    
    // 记录链表或树的节点数
    int binCount = 0;

    // 无限循环,用于处理并发下的重试逻辑
    for (Node<K,V>[] tab = table;;) {
        Node<K,V> f; int n, i, fh;
        
        // 如果 table 还未初始化,则初始化它
        if (tab == null || (n = tab.length) == 0)
            tab = initTable();
        // 如果计算出的桶位为空(即当前桶没有节点),则通过 CAS 操作直接插入新节点
        else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
            if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null)))
                break; // 无需锁定即可插入到空的桶中,插入成功后跳出循环
        }
        // 如果该桶正在进行扩容操作(MOVED 表示桶正在被转移),则协助扩容
        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;
                            // 如果找到 key 相同的节点,则更新值
                            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;
}
remove方法源码
public V remove(Object key) {
    return replaceNode(key, null, null);
}

/* Implementation for the four public remove/replace methods: Replaces node value   * with v, conditional upon match of cv if non-null. If resulting value is null,    * delete.
 */
final V replaceNode(Object key, V value, Object cv) {
    // 计算键的哈希值并扩展哈希值,以减少哈希冲突
    int hash = spread(key.hashCode());
    // 无限循环,用于重试机制,以应对并发修改等情况
    for (Node<K,V>[] tab = table;;) {
        Node<K,V> f; int n, i, fh;
        // 如果哈希表为空,或哈希表的长度为0,或计算的槽位为空
        if (tab == null || (n = tab.length) == 0 ||
            (f = tabAt(tab, i = (n - 1) & hash)) == null)
            break;  // 直接退出,无需替换
        else if ((fh = f.hash) == MOVED)
            // 如果当前槽位正在进行转移操作,则协助数据迁移
            tab = helpTransfer(tab, f);
        else {
            V oldVal = null;
            boolean validated = false;
            // 对当前槽位的头节点加锁,防止其他线程修改
            synchronized (f) {
                // 再次确认当前槽位的头节点是否与之前获取的节点相同
                if (tabAt(tab, i) == f) {
                    if (fh >= 0) {  // 如果是普通链表节点
                        validated = true;
                        // 遍历链表查找对应的键
                        for (Node<K,V> e = f, pred = null;;) {
                            K ek;
                            // 找到匹配的键
                            if (e.hash == hash &&
                                ((ek = e.key) == key ||
                                 (ek != null && key.equals(ek)))) {
                                V ev = e.val;
                                // 如果当前值匹配预期值(cv),则替换或删除
                                if (cv == null || cv == ev ||
                                    (ev != null && cv.equals(ev))) {
                                    oldVal = ev;
                                    if (value != null)  // 替换值
                                        e.val = value;
                                    else if (pred != null)  // 删除节点
                                        pred.next = e.next;
                                    else
                                        setTabAt(tab, i, e.next);  // 删除头节点
                                }
                                break;
                            }
                            pred = e;  // 更新前驱节点
                            if ((e = e.next) == null)  // 遍历到链表末尾
                                break;
                        }
                    }
                    else if (f instanceof TreeBin) {  // 如果是红黑树节点
                        validated = true;
                        TreeBin<K,V> t = (TreeBin<K,V>)f;
                        TreeNode<K,V> r, p;
                        // 查找红黑树中的节点
                        if ((r = t.root) != null &&
                            (p = r.findTreeNode(hash, key, null)) != null) {
                            V pv = p.val;
                            // 如果当前值匹配预期值(cv),则替换或删除
                            if (cv == null || cv == pv ||
                                (pv != null && cv.equals(pv))) {
                                oldVal = pv;
                                if (value != null)  // 替换值
                                    p.val = value;
                                else if (t.removeTreeNode(p))  // 删除节点
                                    setTabAt(tab, i, untreeify(t.first));  // 取消树化
                            }
                        }
                    }
                }
            }
            // 如果操作已经成功验证(节点锁定成功)
            if (validated) {
                if (oldVal != null) {  // 如果找到并修改了节点
                    if (value == null)  // 如果是删除操作
                        addCount(-1L, -1);  // 更新大小计数器
                    return oldVal;  // 返回旧值
                }
                break;  // 如果未找到对应的节点或未修改,退出循环
            }
        }
    }
    return null;  // 未找到或替换的节点
}

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

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

相关文章

TDengine 签约前晨汽车,解锁智能出行的无限潜力

在全球汽车产业转型升级的背景下&#xff0c;智能网联和新能源技术正迅速成为商用车行业的重要发展方向。随着市场对环保和智能化需求的日益增强&#xff0c;企业必须在技术创新和数据管理上不断突破&#xff0c;以满足客户对高效、安全和智能出行的期待。在这一背景下&#xf…

如何通过 PhantomJS 模拟用户行为抓取动态网页内容

引言 随着网页技术的不断进步&#xff0c;JavaScript 动态加载内容已成为网站设计的新常态&#xff0c;这对传统的静态网页抓取方法提出了挑战。为了应对这一挑战&#xff0c;PhantomJS 作为一个无头浏览器&#xff0c;能够模拟用户行为并执行 JavaScript&#xff0c;成为了获…

探索数据结构:初入算法之经典排序算法

&#x1f511;&#x1f511;博客主页&#xff1a;阿客不是客 &#x1f353;&#x1f353;系列专栏&#xff1a;渐入佳境之数据结构与算法 欢迎来到泊舟小课堂 &#x1f618;博客制作不易欢迎各位&#x1f44d;点赞⭐收藏➕关注 一、插入排序 步骤&#xff1a; 从第一个元素开…

OpenAI 刚刚推出 o1 大模型!!突破LLM极限

北京时间 9 月 13 日午夜&#xff0c;OpenAI 正式发布了一系列全新的 AI 大模型&#xff0c;专门用于应对复杂问题。 这一新模型的出现代表了一个重要突破&#xff0c;其具备的复杂推理能力远远超过了以往用于科学、代码和数学等领域的通用模型&#xff0c;能够解决比之前更难的…

Python和R均方根误差平均绝对误差算法模型

&#x1f3af;要点 回归模型误差评估指标归一化均方根误差生态状态指标神经网络成本误差计算气体排放气候算法模型 Python误差指标 均方根误差和平均绝对误差 均方根偏差或均方根误差是两个密切相关且经常使用的度量值之一&#xff0c;用于衡量真实值或预测值与观测值或估…

HarmonyOS开发实战( Beta5.0)骨架屏实现案例实践

鸿蒙HarmonyOS开发往期必看&#xff1a; HarmonyOS NEXT应用开发性能实践总结 最新版&#xff01;“非常详细的” 鸿蒙HarmonyOS Next应用开发学习路线&#xff01;&#xff08;从零基础入门到精通&#xff09; 介绍 本示例介绍通过骨架屏提升加载时用户体验的方法。骨架屏用…

无法加载用户配置文件怎么解决?

你有没有遇到过这种问题&#xff0c;蓝屏提示“User Profile Services服务登录失败。无法加载用户配置文件”。为什么会出现问题呢&#xff1f;可能的原因包括&#xff1a; 用户配置文件损坏&#xff1a;用户的配置文件可能已损坏&#xff0c;导致系统无法读取。 权限问题&…

linux更换阿里镜像源

第一步&#xff1a;进入 /etc/yum.repos.d目录下 cd /etc/yum.repos.d 第二步&#xff1a;编辑 CentOS-Base.repo 打开该文件 vi CentOS-Base.repo 第三步&#xff1a;点击键盘i&#xff0c;进入编辑模式 删除文件的全部内容&#xff1a;将阿里下面配置复制粘贴进取 [base] nam…

Ribbon (WPF)

Ribbon (WPF) 在本文中主要包含以下内容&#xff1a; Ribbon组件和功能应用程序菜单快速访问工具栏增强的工具提示 Ribbon是一个命令栏&#xff0c;它将应用程序的功能组织到应用程序窗口顶部的一系列选项卡中。Ribbon用户界面(UI)增加了特性和功能的可发现性&#xff0c;使用…

神经网络学习笔记——如何设计、实现并训练一个标准的前馈神经网络

1.从零设计并训练一个神经网络https://www.bilibili.com/video/BV134421U77t/?spm_id_from333.337.search-card.all.click&vd_source0b1f472915ac9cb9cdccb8658d6c2e69 一、如何设计、实现并训练一个标准的前馈神经网络&#xff0c;用于手写数字图像的分类&#xff0c;重…

如何制作Vector Vflash中加载的DLL文件--自动解锁刷写过程中27服务

案例背景&#xff1a; vFlash 是一种易于使用的工具&#xff0c;用于对一个或多个 ECU 进行刷写软件。由于方法灵活&#xff0c;它可以支持各种汽车原始设备制造商的不同刷写规范。它支持通过 CAN、CAN FD、FlexRay、LIN、以太网/DoIP 和以太网/SoAd 对 ECU 进行刷写。 vFlas…

SpringSecurity原理解析(六):SecurityConfigurer 解析

1、SecurityConfigurer SecurityConfigurer 在 Spring Security 中是一个非常重要的接口&#xff0c;观察HttpSecurity 中的很多 方法可以发现&#xff0c;SpringSecurity 中的每一个过滤器都是通过 xxxConfigurer 来进行配置的&#xff0c;而 这些 xxxConfigurer 其实都是 Sec…

针对Docker容器的可视化管理工具—DockerUI

目录 ⛳️推荐 前言 1. 安装部署DockerUI 2. 安装cpolar内网穿透 3. 配置DockerUI公网访问地址 4. 公网远程访问DockerUI 5. 固定DockerUI公网地址 ⛳️推荐 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下…

GBI(生成式商业智能)实际业务生产落地运用上的探索和实践

前言 最近在探索如何发展AI在业务上的驱动力时了解到了生成式商业智能这一概念&#xff0c;同时本人也在探索ChatBI这一技术的实际落地运用&#xff0c;其实二者几乎在实现效果层面是一个意思&#xff0c;GBI(Generative Business Intelligence)是偏向业务方面&#xff0c;而C…

[000-01-008].第05节:OpenFeign高级特性-超时控制

我的后端学习大纲 SpringCloud学习大纲 1.1.OpenFeign超时的情况&#xff1a; 在Spring Cloud微服务架构中&#xff0c;大部分公司都是利用OpenFeign进行服务间的调用&#xff0c;而比较简单的业务使用默认配置是不会有多大问题的&#xff0c;但是如果是业务比较复杂&#xff…

UiBot教程:实现复杂流程图的高效方法

在自动化测试和RPA&#xff08;机器人流程自动化&#xff09;领域&#xff0c;使用UiBot绘制复杂流程图是日常工作中常见的挑战之一。如何在繁杂的逻辑中保持高效&#xff1f;如何实现复杂流程的自动化设计而不迷失于其中&#xff1f;这是许多测试工程师和自动化开发者所面临的…

区块链之变:揭秘Web3对互联网的改变

传统游戏中&#xff0c;玩家的虚拟资产&#xff08;如角色、装备&#xff09;通常由游戏公司控制&#xff0c;玩家无法真正拥有这些资产或进行交易。而在区块链游戏中&#xff0c;虚拟资产通过去中心化技术记录在区块链上&#xff0c;玩家对其拥有完全的所有权&#xff0c;并能…

Loki 分布式日志中心服务

目录 Loki 是什么 Loki 配置文件介绍 Loki 安装 Promtail 配置文件介绍 Promtail 安装 Loki 整合 Grafana Loki 是什么 Loki 是一个专为日志聚合和查询设计的开源分布式日志管理系统&#xff0c;由 Grafana Labs 开发。它与 Prometheus 类似&#xff0c;但用于处理日志&a…

决策树实战

文章目录 一、入门基础案例二、基于sklearn的决策树模型2.1sklearn中的决策树实现2.2分类型决策树&#xff1a;DecisionTreeClassifier2.2.1重要参数2.2.2重要属性与接口2.2.3基本案例&#xff1a;wine葡萄酒数据集 2.3回归型决策树&#xff1a;DecisionTreeRegressor2.3.1重要…

大学选修课无人机航拍技术与技巧怎么样?

在当今这个视觉盛行的时代&#xff0c;无人机航拍技术以其独特的视角和非凡的创意能力&#xff0c;正逐步成为影视制作、新闻报道、地理测绘、环境监测及个人记录生活等领域不可或缺的工具。为此&#xff0c;本大学特设《无人机航拍技术与技巧》选修课&#xff0c;旨在通过系统…