首页 > 编程语言 >ConcurrentHashMap 核心源码解析

ConcurrentHashMap 核心源码解析

时间:2024-02-29 10:55:32浏览次数:17  
标签:Node ... ConcurrentHashMap int 源码 数组 拷贝 解析 节点

废话不多说,直接看代码

类名

与 HashMap 很相似,数组、链表结构几乎相同,都实现了 Map 接口,继承了 AbstractMap 抽象类,大多数的方法也都是相同的

public class ConcurrentHashMap<K,V> extends AbstractMap<K,V> implements ConcurrentMap<K,V>, Serializable

核心方法

Node方法

保存key,value及key的hash值的数据结构,其中value和next都用volatile修饰,保证可见性

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(int hash, K key, V val, Node<K,V> next) {... }
        public final K getKey() {... }
        public final V getValue() {... }
        public final int hashCode() {... }
        public final String toString() {...}
        public final V setValue(V value) {...}
        public final boolean equals(Object o) {...}
        Node<K,V> find(int h, Object k) {...}
    }

ForwardingNode

只有table发生扩容的时候,ForwardingNode才会发挥作用,作为一个占位符放在table中表示当前节点为null或则已经被移动

  static final class ForwardingNode<K,V> extends Node<K,V> {
        final Node<K,V>[] nextTable;
        ForwardingNode(Node<K,V>[] tab) {
            super(MOVED, null, null);
            this.nextTable = tab;
        }

        Node<K,V> find(int h, Object k) {...}
    }

新增执行流程

  1. 若数组空,则初始化,完成之后,转2
  2. 计算当前桶位是否有值
    2.1 无,则 CAS 创建,失败后继续自旋,直到成功
    2.2 有,转3
  3. 判断桶位是否为转移节点(扩容ing)
    3.1 是,则一直自旋等待扩容完成,之后再新增
    3.2 否,转4
  4. 桶位有值,对当前桶位加synchronize锁
    4.1 链表,新增节点到链尾
    4.2 红黑树,红黑树版方法新增
  5. 新增完成之后,检验是否需要扩容

扩容执行流程

private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
    // 旧数组的长度
    int n = tab.length, stride;
    if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
        stride = MIN_TRANSFER_STRIDE; // subdivide range
    // 如果新数组为空,初始化,大小为原数组的两倍,n << 1
    if (nextTab == null) {            // initiating
        try {
            @SuppressWarnings("unchecked")
            Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1];
            nextTab = nt;
        } catch (Throwable ex) {      // try to cope with OOME
            sizeCtl = Integer.MAX_VALUE;
            return;
        }
        nextTable = nextTab;
        transferIndex = n;
    }
    // 新数组长度
    int nextn = nextTab.length;
    // 若原数组上是转移节点,说明该节点正在被扩容
    ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);
    boolean advance = true;
    boolean finishing = false; // to ensure sweep before committing nextTab
    // 自旋,i 值会从原数组的最大值递减到 0
    for (int i = 0, bound = 0;;) {
        Node<K,V> f; int fh;
        while (advance) {
            int nextIndex, nextBound;
            // 结束循环的标志
            if (--i >= bound || finishing)
                advance = false;
            // 已经拷贝完成
            else if ((nextIndex = transferIndex) <= 0) {
                i = -1;
                advance = false;
            }
            // 每次减少 i 的值
            else if (U.compareAndSwapInt
                     (this, TRANSFERINDEX, nextIndex,
                      nextBound = (nextIndex > stride ?
                                   nextIndex - stride : 0))) {
                bound = nextBound;
                i = nextIndex - 1;
                advance = false;
            }
        }
        // if 任意条件满足说明拷贝结束了
        if (i < 0 || i >= n || i + n >= nextn) {
            int sc;
            // 拷贝结束,直接赋值,因为每次拷贝完一个节点,都在原数组上放转移节点,所以拷贝完成的节点的数据一定不会再发生变化
            // 原数组发现是转移节点,是不会操作的,会一直等待转移节点消失之后在进行操作
            // 也就是说数组节点一旦被标记为转移节点,是不会再发生任何变动的,所以不会有任何线程安全的问题
            // 所以此处直接赋值,没有任何问题。
            if (finishing) {
                nextTable = null;
                table = nextTab;
                sizeCtl = (n << 1) - (n >>> 1);
                return;
            }
            if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
                if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
                    return;
                finishing = advance = true;
                i = n; // recheck before commit
            }
        }
        else if ((f = tabAt(tab, i)) == null)
            advance = casTabAt(tab, i, null, fwd);
        else if ((fh = f.hash) == MOVED)
            advance = true; // already processed
        else {
            synchronized (f) {
                // 节点的拷贝
                if (tabAt(tab, i) == f) {
                    Node<K,V> ln, hn;
                    if (fh >= 0) {
                        int runBit = fh & n;
                        Node<K,V> lastRun = f;
                        for (Node<K,V> p = f.next; p != null; p = p.next) {
                            int b = p.hash & n;
                            if (b != runBit) {
                                runBit = b;
                                lastRun = p;
                            }
                        }
                        if (runBit == 0) {
                            ln = lastRun;
                            hn = null;
                        }
                        else {
                            hn = lastRun;
                            ln = null;
                        }
                        // 如果节点只有单个数据,直接拷贝,如果是链表,循环多次组成链表拷贝
                        for (Node<K,V> p = f; p != lastRun; p = p.next) {
                            int ph = p.hash; K pk = p.key; V pv = p.val;
                            if ((ph & n) == 0)
                                ln = new Node<K,V>(ph, pk, pv, ln);
                            else
                                hn = new Node<K,V>(ph, pk, pv, hn);
                        }
                        // 在新数组位置上放置拷贝的值
                        setTabAt(nextTab, i, ln);
                        setTabAt(nextTab, i + n, hn);
                        // 在老数组位置上放上 ForwardingNode 节点
                        // put 时,发现是 ForwardingNode 节点,就不会再动这个节点的数据了
                        setTabAt(tab, i, fwd);
                        advance = true;
                    }
                    // 红黑树的拷贝
                    else if (f instanceof TreeBin) {
                        // 红黑树的拷贝工作,同 HashMap 的内容,代码忽略
                        ...
                        // 在老数组位置上放上 ForwardingNode 节点
                        setTabAt(tab, i, fwd);
                        advance = true;
                    }
                }
            }
        }
    }
}
  1. 首先把原数组的值全部拷贝到扩容之后的新数组,先从数组的队尾开始拷贝
  2. 拷贝数组的槽点时,先把原数组槽点锁住,成功拷贝到新数组时,把原数组槽点赋值为转移节点
  3. 这时如果有新数据正好需要 put 到该槽点时,发现槽点为转移节点,就会一直等待,所以在扩容完成之前,该槽点对应的数据是不会发生变化的
  4. 从数组的尾部拷贝到头部,每拷贝成功一次,就把原数组中的节点设置成转移节点
  5. 直到所有数组数据都拷贝到新数组时,直接把新数组整个赋值给数组容器,拷贝完成。

标签:Node,...,ConcurrentHashMap,int,源码,数组,拷贝,解析,节点
From: https://www.cnblogs.com/jietang64/p/18042951

相关文章

  • Vue源码解读(预):手写一个简易版Vue
    Vue源码解读(预):手写一个简易版Vue MVVM设计模式,是由MVC、MVP等设计模式进化而来,M-数据模型(Model),VM-视图模型(ViewModel),V-视图层(View)。MVVM的核心是ViewModel层,它就像是一个中转站(valueconverter),负责转换Model中的数据对象来让数据变得更容易管理和使用,该层向上与视......
  • Java 包和 API 深度解析:组织代码,避免命名冲突
    Java包和APIJava中的包用于将相关的类分组在一起。可以将其视为文件目录中的一个文件夹。我们使用包来避免名称冲突,并编写更易于维护的代码。包分为两类:内置包(来自JavaAPI的包)用户定义的包(创建自己的包)内置包JavaAPI是一个预先编写的类库,可以在Java开发环境中......
  • Eureka源码分析
     注册中心1、Register:服务注册  当Eureka客户端向EurekaServer注册时,它提供自身的元数据,比如IP地址、端口,运行状况指示符URL,主页等    1.1、服务端注册    会拉取配置的注册中心地址,向附近注册服务注册    1.2、客户端注册客户端第一次续约会失败,因......
  • 龙哥量化:通达信(副图)筹码线起爆指标公式源码
    如果您需要代写公式,请联系我。龙哥QQ:591438821龙哥微信:Long622889这是个副图公式,如果需要改成选股公式,我随时在线 筹码密集度:(COST(90+(100-90)/2)-COST((100-90)/2))/(COST(90+(100-90)/2)+COST((100-90)/2))+CLOSE,COLORYELLOW,LINETHICK2;X_1:=REF(HHV(筹码密集度,......
  • 龙哥量化:通达信(副图)选股公式源码均线、macd、skdj、kdj、rsi、dmi、cci,vol共振
    如果您需要代写公式,请联系我。龙哥QQ:591438821龙哥微信:Long622889 这个公式是几个指标的共振。新建一个条件选股公式,新建一个副图公式,都用下面的源码; {取消的股票 }T1:=IF(NAMELIKE('ST'),0,1)ANDIF(NAMELIKE('*'),0,1);T2:=NOT(CODELIKE('688'));T3:=NOT(CODELI......
  • 掌握字符与字符串:C语言中的神奇函数解析(二)
    ✨✨欢迎大家来到贝蒂大讲堂✨✨......
  • Uniapp商城小程序源码+运行实例+下载资源包全开源
    商城小程序源码是一种可以用来开发商城类小程序的代码文件或项目,它包含了商城小程序的基本功能和界面设计等内容。通过使用商城小程序源码,开发者可以节省开发时间和成本,快速构建和定制自己的商城小程序。源码通常包括用户登录、商品浏览、购物车、订单管理、支付功能等,以满足......
  • Java中使用Jsoup实现网页内容爬取与Html内容解析并使用EasyExcel实现导出为Excel文件
    场景Pythont通过request以及BeautifulSoup爬取几千条情话:https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/87348030Node-RED中使用html节点爬取HTML网页资料之爬取Node-RED的最新版本:https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/124182289Jsoup......
  • arm64-ubuntu2204-opencv4.7.0源码编译
    参考:https://blog.csdn.net/weixin_43863869/article/details/128552342https://blog.csdn.net/weixin_39956356/article/details/102643415https://blog.csdn.net/quicmous/article/details/112714641 cdopencv-4.7.0 sudoapt-getinstallbuild-essentiallibgtk2.0-d......
  • 解析Spring中的循环依赖问题:再探三级缓存(AOP)
    前言在之前的内容中,我们简要探讨了循环依赖,并指出仅通过引入二级缓存即可解决此问题。然而,你可能会好奇为何在Spring框架中还需要引入三级缓存singletonFactories。在前述总结中,我已经提供了答案,即AOP代理对象。接下来,我们将深入探讨这一话题。AOP在Spring框架中,AOP的实现是通......