JDK 7 ConcurrentHashMap

news2025/4/27 14:08:04

目录

概述

构造器分析

 put 流程

get 流程

size 计算流程


概述

JDK1.7中的ConcurrentHashMap间接地实现了Map,并将每一个元素称为分段锁segment,每个segment都是一个HashEntry<K,V>数组,称为table,table的每个元素都是一个HashEntry的单向队列。

「HashTable是给整个容器加锁,ConcurrentHashMap是给每个segment加锁,」当一个线程修改segment 0时,其他线程也可以修改其它segment,即 只要不同的线程同一时刻访问的是不同的segment,就不会发生写冲突,比HashMap性能更好。

它维护了一个 segment 数组,每个 segment 对应一把锁

  • 优点:如果多个线程访问不同的 segment,实际是没有冲突的,这与 jdk8 中是类似的
  • 缺点:Segments 数组默认大小为16,这个容量初始化指定后就不能改变了,并且不是懒惰初始化

构造器分析

    public ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel) {
        if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)
            throw new IllegalArgumentException();
        if (concurrencyLevel > MAX_SEGMENTS)
            concurrencyLevel = MAX_SEGMENTS;
        // ssize 必须是 2^n, 即 2, 4, 8, 16 ... 表示了 segments 数组的大小

        int sshift = 0;
        int ssize = 1;
        while (ssize < concurrencyLevel) {
            ++sshift;
            ssize <<= 1;
        }
        // segmentShift 默认是 32 - 4 = 28

        this.segmentShift = 32 - sshift;
        // segmentMask 默认是 15 即 0000 0000 0000 1111

        this.segmentMask = ssize - 1;
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        int c = initialCapacity / ssize;
        if (c * ssize < initialCapacity)
            ++c;
        int cap = MIN_SEGMENT_TABLE_CAPACITY;
        while (cap < c)
            cap <<= 1;
        // 创建 segments and segments[0]

        Segment<K,V> s0 =

                new Segment<K,V>(loadFactor, (int)(cap * loadFactor),
                        (HashEntry<K,V>[])new HashEntry[cap]);
        Segment<K,V>[] ss = (Segment<K,V>[])new Segment[ssize];
        UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0]

        this.segments = ss;
    }

构造完成,如下图所示

可以看到 ConcurrentHashMap 没有实现懒惰初始化,空间占用不友好

其中 this.segmentShift 和 this.segmentMask 的作用是决定将 key 的 hash 结果匹配到哪个 segment

例如,根据某一 hash 值求 segment 位置,先将高位向低位移动 this.segmentShift 位

结果再与 this.segmentMask 做位于运算,最终得到 1010 即下标为 10 的 segment

 put 流程

    public V put(K key, V value) {
        Segment<K, V> s;
        if (value == null)
            throw new NullPointerException();
        int hash = hash(key);
        // 计算出 segment 下标

        int j = (hash >>> segmentShift) & segmentMask;

        // 获得 segment 对象, 判断是否为 null, 是则创建该 segment

        if ((s = (Segment<K, V>) UNSAFE.getObject
                (segments, (j << SSHIFT) + SBASE)) == null) {
            // 这时不能确定是否真的为 null, 因为其它线程也发现该 segment 为 null,

            // 因此在 ensureSegment 里用 cas 方式保证该 segment 安全性

            s = ensureSegment(j);
        }
        // 进入 segment 的put 流程

        return s.put(key, hash, value, false);
    }

segment 继承了可重入锁(ReentrantLock),它的 put 方法为

    final V put(K key, int hash, V value, boolean onlyIfAbsent) {
        // 尝试加锁
        HashEntry<K, V> node = tryLock() ? null :
                // 如果不成功, 进入 scanAndLockForPut 流程
                // 如果是多核 cpu 最多 tryLock 64 次, 进入 lock 流程
                // 在尝试期间, 还可以顺便看该节点在链表中有没有, 如果没有顺便创建出来
                scanAndLockForPut(key, hash, value);
        // 执行到这里 segment 已经被成功加锁, 可以安全执行
        V oldValue;
        try {
            HashEntry<K, V>[] tab = table;
            int index = (tab.length - 1) & hash;
            HashEntry<K, V> first = entryAt(tab, index);
            for (HashEntry<K, V> e = first; ; ) {
                if (e != null) {
                    // 更新
                    K k;
                    if ((k = e.key) == key ||

                            (e.hash == hash && key.equals(k))) {
                        oldValue = e.value;
                        if (!onlyIfAbsent) {
                            e.value = value;
                            ++modCount;
                        }
                        break;
                    }
                    e = e.next;
                } else {
                    // 新增
                    // 1) 之前等待锁时, node 已经被创建, next 指向链表头
                    if (node != null)
                        node.setNext(first);
                    else
                        // 2) 创建新 node
                        node = new HashEntry<K, V>(hash, key, value, first);
                    int c = count + 1;
                    // 3) 扩容
                    if (c > threshold && tab.length < MAXIMUM_CAPACITY)
                        rehash(node);
                    else
                        // 将 node 作为链表头
                        setEntryAt(tab, index, node);
                    ++modCount;
                    count = c;
                    oldValue = null;
                    break;
                }
            }
        } finally {
            unlock();
        }
        return oldValue;
    }

rehash 流程

发生在 put 中,因为此时已经获得了锁,因此 rehash 时不需要考虑线程安全

    private void rehash(HashEntry<K,V> node) {
        HashEntry<K,V>[] oldTable = table;
        int oldCapacity = oldTable.length;
        int newCapacity = oldCapacity << 1;
        threshold = (int)(newCapacity * loadFactor);
        HashEntry<K,V>[] newTable =

                (HashEntry<K,V>[]) new HashEntry[newCapacity];
        int sizeMask = newCapacity - 1;
        for (int i = 0; i < oldCapacity ; i++) {
            HashEntry<K,V> e = oldTable[i];
            if (e != null) {
                HashEntry<K,V> next = e.next;
                int idx = e.hash & sizeMask;
                if (next == null) // Single node on list

                    newTable[idx] = e;
                else { // Reuse consecutive sequence at same slot

                    HashEntry<K,V> lastRun = e;
                    int lastIdx = idx;
                    // 过一遍链表, 尽可能把 rehash 后 idx 不变的节点重用 

                    for (HashEntry<K,V> last = next;
                         last != null;
                         last = last.next) {
                        int k = last.hash & sizeMask;
                        if (k != lastIdx) {
                            lastIdx = k;
                            lastRun = last;
                        }
                    }
                    newTable[lastIdx] = lastRun;
                    // 剩余节点需要新建

                    for (HashEntry<K,V> p = e; p != lastRun; p = p.next) {
                        V v = p.value;
                        int h = p.hash;
                        int k = h & sizeMask;
                        HashEntry<K,V> n = newTable[k];
                        newTable[k] = new HashEntry<K,V>(h, p.key, v, n);
                    }
                }
            }
        }
        // 扩容完成, 才加入新的节点

        int nodeIndex = node.hash & sizeMask; // add the new node

        node.setNext(newTable[nodeIndex]);
        newTable[nodeIndex] = node;

        // 替换为新的 HashEntry table

        table = newTable;
    }

get 流程

get 时并未加锁,用了 UNSAFE 方法保证了可见性,扩容过程中,get 先发生就从旧表取内容,get 后发生就从新 表取内容

 public V get(Object key) {
        Segment<K, V> s; // manually integrate access methods to reduce overhead

        HashEntry<K, V>[] tab;
        int h = hash(key);
        // u 为 segment 对象在数组中的偏移量

        long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;
        // s 即为 segment

        if ((s = (Segment<K, V>) UNSAFE.getObjectVolatile(segments, u)) != null &&

                (tab = s.table) != null) {
            for (HashEntry<K, V> e = (HashEntry<K, V>) UNSAFE.getObjectVolatile

                    (tab, ((long) (((tab.length - 1) & h)) << TSHIFT) + TBASE);
                 e != null; e = e.next) {
                K k;
                if ((k = e.key) == key || (e.hash == h && key.equals(k)))
                    return e.value;
            }
        }
        return null;
    }

size 计算流程

  • 计算元素个数前,先不加锁计算两次,如果前后两次结果如一样,认为个数正确返回
  • 如果不一样,进行重试,重试次数超过 3,将所有 segment 锁住,重新计算个数返回 
    public int size() {
        // Try a few times to get accurate count. On failure due to
        // continuous async changes in table, resort to locking.
        final Segment<K,V>[] segments = this.segments;
        int size;
        boolean overflow; // true if size overflows 32 bits

        long sum; // sum of modCounts

        long last = 0L; // previous sum

        int retries = -1; // first iteration isn't retry

        try {
            for (;;) {
                if (retries++ == RETRIES_BEFORE_LOCK) {
                    // 超过重试次数, 需要创建所有 segment 并加锁

                    for (int j = 0; j < segments.length; ++j)
                        ensureSegment(j).lock(); // force creation

                }
                sum = 0L;
                size = 0;
                overflow = false;
                for (int j = 0; j < segments.length; ++j) {
                    Segment<K,V> seg = segmentAt(segments, j);
                    if (seg != null) {
                        sum += seg.modCount;
                        int c = seg.count;
                        if (c < 0 || (size += c) < 0)
                            overflow = true;
                    }
                }
                if (sum == last)
                    break;
                last = sum;
            }
        } finally {
            if (retries > RETRIES_BEFORE_LOCK) {
                for (int j = 0; j < segments.length; ++j)
                    segmentAt(segments, j).unlock();
            }
        }
        return overflow ? Integer.MAX_VALUE : size;
    }

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

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

相关文章

【雕爷学编程】Arduino动手做(149)---MAX9814咪头传感器模块2

37款传感器与执行器的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止这37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&am…

MyBatis PostgreSQL实现数组类型的操作

我的GitHub&#xff1a;Powerveil GitHub 我的Gitee&#xff1a;Powercs12 (powercs12) - Gitee.com 皮卡丘每天学Java 最近在学习数据库PostgreSQL&#xff0c;遇到如何实现对数组类型的数据操作&#xff0c;试着自己尝试学习实现。 话不多说&#xff0c;直接撸代码。 建表…

linux下一个iic驱动(按键+点灯)-互斥

一、前提&#xff1a; 硬件部分&#xff1a; 1. rk3399开发板&#xff0c;其中的某一路iic&#xff0c;这个作为总线的主控制器 2. gd32单片机&#xff0c;其中的某一路iic&#xff0c;从设备。主要是按键上报和灯的亮灭控制。&#xff08;按键大约30个&#xff0c;灯在键的…

新手杯—easy_base

0x00 前言 CTF 加解密合集&#xff1a;CTF 加解密合集 0x01 题目 0XezFWZfNXafRjNlNXYit3dvh2cmR3Y0x02 Write Up 先倒序 然后base64解码 以上

Self-Attention Cross-Attention

transformer的细节到底是怎么样的&#xff1f;Transformer 连环18问&#xff01; 4.1 从功能角度&#xff0c;Transformer Encoder的核心作用是提取特征&#xff0c;也有使用Transformer Decoder来提取特征。例如&#xff0c;一个人学习跳舞&#xff0c;Encoder是看别人是如何…

智能网卡在分布式 SDN 网络的应用与实践 | 龙蜥技术

编者按&#xff1a;当前智能网卡能够加速数据处理和传输&#xff0c;并能实现网络、存储和安全等功能卸载&#xff0c;在云计算领域得到广泛的应用。今天&#xff0c;浪潮数据云计算网络架构师王培辉带大家了解智能网卡加速原理和以及在浪潮分布式 SDN 网络加速的应用&#xff…

我连夜咨询了30个老同学,学IT上培训班到底有用么?

文章目录 一、背景二、学习IT上培训班的益处2.1 IT行业本身还不错2.2 获取到系统的专业知识2.3 获取到实战经验2.4 获取到网络资源和支持2.5 获取到职业发展指导2.6 建立初步的职业圈子人脉 三、学习IT上培训班的风险3.1 质量风险3.2 课程更新速度风险3.2 缺乏互动与实践机会风…

积分微分电路

积分微分电路 通过写出时域的推导&#xff0c;再到频域&#xff0c;详细介绍了积分微分的频率响应的推导&#xff0c;手绘了bode图&#xff0c;并仿真电路得到对应的结果。积分的频率响应&#xff1a;频率增加10倍&#xff0c;增益下降20db。输出相位超前输入相位90度。微分的…

GPT-4 最强竞争对手,Claude 杀疯了!

公众号关注 “GitHubDaily” 设为 “星标”&#xff0c;每天带你逛 GitHub&#xff01; 在今年早些时候&#xff0c;ChatGPT、Bard、Claude 等大语言模型&#xff0c;在 AI 领域呈三权鼎立之势&#xff0c;无人能出其右&#xff0c;被视为是能力表现最为卓越的 3 款 AI 聊天机器…

阿里云无影云电脑具体价格_云桌面不同配置1元报价

阿里云无影云电脑配置费用&#xff0c;4核8G企业办公型云电脑可以免费使用3个月&#xff0c;无影云电脑地域不同费用不同&#xff0c;无影云电脑是由云桌面配置、云盘、互联网访问带宽、AD Connector、桌面组共用桌面session等费用组成&#xff0c;阿里云百科分享阿里云无影云电…

大模型的“第一性原理”:技术创新与社会价值的接轨

随着时间来到2023年第三季度&#xff0c;国产大模型已经达到100多个&#xff0c;“百模大战”正式开启。 大模型&#xff0c;我们有了很多选择&#xff0c;也开始呈现出某种同质化。除了拼参数、比背景、看榜单&#xff0c;有没有其他方法&#xff0c;让我们更好地判断一个大模…

解决Gson解析json字符串,Integer变为Double类型的问题

直接上代码记录下。我代码里没有Gson包&#xff0c;用的是nacos对Gson的封装&#xff0c;只是包不同&#xff0c;方法都一样 import com.alibaba.nacos.shaded.com.google.common.reflect.TypeToken; import com.alibaba.nacos.shaded.com.google.gson.*;import java.util.Map;…

经典CNN(一):ResNet-50算法实战与解析

&#x1f368; 本文为&#x1f517;365天深度学习训练营中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊|接辅导、项目定制 1 ResNet理论 深度残差网络ResNet(deep residual network)在2015年由何凯明等提出&#xff0c;因为它简单与实用并存&#xff0c;随后很多研究…

Hutool工具类 -集常用工具类为一体 - 工具类之大成

文章目录 说在前面的话简介gitee介绍项目介绍 网址gtiee 网址github 网址 安装pom依赖引入 &#xff1a;下载jar 文档中文文档中文备用文档参考API视频介绍 部分截图首页包含组件(总)IO流相关部分工具类(Util)集合类HTTP客户端 功能不再一一赘述和截图&#xff0c;具体请查看官…

详解TCP协议

TCP协议段格式 序号和确认序号&#xff1a;在真实服务器和客服端通信过程中请求是并行执行的&#xff0c;这会导致到达是乱序的&#xff0c;所以才会有序号这个东西&#xff0c;确认序号是对方应答时返回的&#xff0c;例如序号发送到1&#xff0c;确认序号会返回2&#xff0c;…

计算机网络 day6 arp病毒 - ICMP协议 - ping命令 - Linux手工配置IP地址

目录 arp协议 arp病毒\欺骗 arp病毒的运行原理 arp病毒产生的后果&#xff1a; 解决方法&#xff1a; ICMP协议 ICMP用在哪里&#xff1f; ICMP协议数据的封装过程 ​编辑 为什么icmp协议封装好数据后&#xff0c;还要加一个ip包头&#xff0c;再使用ip协议再次进…

springboot农机电招平台

本系统为了数据库结构的灵活性所以打算采用MySQL来设计数据库&#xff0c;而java技术&#xff0c;B/S架构则保证了较高的平台适应性。本文主要介绍了本系统的开发背景&#xff0c;所要完成的功能和开发的过程&#xff0c;主要说明了系统设计的重点、设计思想。 本系统主要是设…

关于java垃圾回收的小结

一、为什么要有垃圾回收 我们每次创建对象都需要在栈上开辟空间&#xff0c;堆上使用内存&#xff0c;如果我们只是开辟了这个空间&#xff0c;而不去释放他&#xff0c;那么再大的内存和空间也会有满的一天&#xff0c;所以我们在Java中引入了GC&#xff08;垃圾回收机制&…

Foxit PDF ActiveX 5.9.8 Crack

Foxit PDF SDK ActiveX 即时添加PDF显示功能至Windows应用程序&#xff0c;快速投放市场&#xff0c;可视化编程组件功能强大且易于使用的PDF软件开发工具包 对于刚接触PDF或不愿投入过多精力学习PDF技术的产品管理者及开发者来说&#xff0c;Foxit PDF SDK ActiveX无疑是理想…

中国1km分辨率逐月平均气温数据集(1901-2022)

时间分辨率月空间分辨率1km - 10km共享方式开放获取数据大小9.71 GB数据时间范围 1901.1-2022.12 数据集摘要 该数据为中国逐月平均温度数据,空间分辨率为0.0083333(约1km),时间为1901.1-2022.12。数据格式为NETCDF,即.nc格式。数据单位为0.1 ℃。该数据集是根据CRU发布的…