深入ConcurrrentHashMap三

深入ConcurrentHashMap讲得是如何put元素到ConcurrentHashMap中。

这篇主要分析在put元素的时候,需要扩容这时ConcurrentHashMap是如何做的。

先介绍下ConcurrentHashMap的主要思路,步骤如下:

1.在put的时候,如果需要新建HashEntry结点插入到HashEntry中时,这时候如果满足扩容条件则进入下面第2步

2.扩容调用的是Segment的rehash方法,只对某个segment扩容。这时需要新建一个segment的HashEntry数组,大小为原来2倍大。

3.然后对原有HashEntry数组中的元素进行重hash,ConcurrentHashMap这里做了优化。

 即如果原有的在HashEntry数组中某一个HashEntry链,如果里面的元素,比如有三个元素a->b->c。这里如果重hash后,a,b,c还是在新数组的相同下标时,可以

想办法重用这几个结点。

下面举例来说明,假设现在一个ConcurrentHashMap,总容量为16,有四个Segment,每个Segment中HashEntry数组大小为4,每个segment的threshold扩容阀值为3。

现在其中一个Segment1的HashEntry存储情况如下:

这里可以看到segment1中HashEntry数组已经使用大小为3。其中index0位置的HashEntry因为有hash冲突所以以链式来解决冲突,这里存储了a,b,c,d,e元素。

index1及index2的hashEntry分别存储了h1,f1两个元素。

这里需要说明的是在做rehash的时候,需要保证尽量不让reader线程受影响。

rehash的时候会先创建一个大小为原来两倍的新数组,然后对之前所有元素进行重hash,这个过程中所有的元素重hash后放入新数组位置都是通过新建一个HashEntry来做,以最小化影响之前的reader线程。

这里假设要put进一个元素到segment1中,key值为p,value为pv。并且这个元素被hash到HashEntry数组的index3位置上,因此需要重建一个HashEntry结点。

这里会先将当前segment1的count值加1,然后判断是否该扩容,这里由于count加1后为4,所以它已经大于上述讲到的threshold阀值,因此需要进行扩容。

扩容时会创建一个大小为8的HashEntry数组,然后分别对原有HashEntry数组中的每个元素进行重hash到新数组中。

以分析segment1的index0的HashEntry链为例。可以看到有元素: a->b->c->d->e

最简单粗暴的重hash方法是:

 1.首先对a重hash,这里假设最终a要存储到新数组位置index4中,因此要创建一个HashEntry用于存储key a及相应value。它的next指针指向新数组index4上的首个

HashEntry,这里为null。

2.然后对b进行重hash,假设还是放在新数组位置index4中。这时创建一个新HashEntry,并将它的next指针指向index4的首结点,这里为a。

这步完成后,index4中元素情况如下:

b->a

3.依次处理完c,d,e。假设c,d,e都还是在新数组index4中。

有没有发现a,b,c,d,e,还是被分配在新数组同个位置。因此对它们的创建及重新指向是没有必要的。

事实上ConcurrentHashMap会有一个指针。

在这个指针指向的结点之前都是认为它们存储在新数组不同位置,因此对于这部分结点还是要新建HashEntry结点及重新调整next指针。

在这个指针结点之后,则被认为是被分配在新数组相同位置,因此它们这部分链条结点可以重用。

我们举例说明,假设a,b,c结点它们最终放在新数组不同位置,而d和e放在新数组相同位置。

因此对于d和e只需做以下动作:

新数组[i]=d

即这里直接将新数组下标i的引用指向了元素d。

能这样做的原因是,rehash后由于扩容后新数组大小为原来2倍。因此对于每个元素在新数组的位置,要么是在原先位置,要么是原先位置加上原先大小.

这里d,e在原先的位置0上,扩容后在新数组的位置4上。之后对于原有数组的下标1,2进行rehash时,它们不可能在被放入到新数组的位置0中。

rehash源码如下:

private void rehash(HashEntry<K,V> node) {
            /*
             * Reclassify nodes in each list to new table.  Because we
             * are using power-of-two expansion, the elements from
             * each bin must either stay at same index, or move with a
             * power of two offset. We eliminate unnecessary node
             * creation by catching cases where old nodes can be
             * reused because their next fields won't change.
             * Statistically, at the default threshold, only about
             * one-sixth of them need cloning when a table
             * doubles. The nodes they replace will be garbage
             * collectable as soon as they are no longer referenced by
             * any reader thread that may be in the midst of
             * concurrently traversing table. Entry accesses use plain
             * array indexing because they are followed by volatile
             * table write.
             */
            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;
                        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;
                        // Clone remaining nodes
                        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;
            table = newTable;
        }

深入ConcurrrentHashMap三

时间: 2024-08-29 16:28:55

深入ConcurrrentHashMap三的相关文章

angularJs中关于ng-class的三种使用方式说明

在开发中我们通常会遇到一种需求:一个元素在不同的状态需要展现不同的样子. 而在这所谓的样子当然就是改变其css的属性,而实现能动态的改变其属性值,必然只能是更换其class属性 这里有三种方法: 第一种:通过数据的双向绑定(不推荐) 第二种:通过对象数组 第三种:通过key/value 下面简单说下这三种: 第一种:通过数据的双向绑定 实现方式: function changeClass(){   $scope.className = "change2"; } <div clas

三百六十度全景图如何拍摄?

三百六十度全景图如何拍摄?随着全景技术的发展,全景拍摄也成为了一种十分新潮的摄影方式.全景摄影也有很多学问,而且随着全景照片的用途越来越多,拍摄全景的设备也越来越多.今天我们就介绍几种十分另类的360全景图拍摄方法,这些酷雷曼360全景图拍摄方法让你大开眼界. 工具/原料 相机 鱼眼镜头 云台 三角支架 方法/步骤 1 吊锤辅助360全景图拍摄方法 吊线保证拍摄时相机以节点旋转,使用吊线进行全景拍摄线不要太长,50CM以内比较容易控制,有时也到一米多在胸口位置进行拍摄,重锤容易晃动,很难对准.吊

关于SVM数学细节逻辑的个人理解(三) :SMO算法理解

第三部分:SMO算法的个人理解 接下来的这部分我觉得是最难理解的?而且计算也是最难得,就是SMO算法. SMO算法就是帮助我们求解: s.t.   这个优化问题的. 虽然这个优化问题只剩下了α这一个变量,但是别忘了α是一个向量,有m个αi等着我们去优化,所以还是很麻烦,所以大神提出了SMO算法来解决这个优化问题. 关于SMO最好的资料还是论文<Sequential Minimal Optimization A Fast Algorithm for Training Support Vector

三件软件作品评价

先交代三件软件作品的相关资料.   软件一 软件二 软件三 软件名称 蜗牛词典APP 24点小游戏APP 物理实验网站 学校 2017集美大学1412软工实践  集美大学1411 北京航天航空大学计算机学院 团队名称 SNS1412 hexagon 软剑攻城队 团队博客地址 http://www.cnblogs.com/jmu-sns/ http://www.cnblogs.com/24app/ http://www.cnblogs.com/buaase/ Git地址 https://codin

MVC4 自定义错误页面(三)

一.概述 MVC4框架自带了定义错误页,该页面位于Shared/Error,该页面能够显示系统未能捕获的异常,如何才能使用该页面: 二.使用步骤: 1.配置WebConfig文件,在System.Web节点下加上 <customErrors mode="On"  defaultRedirect="~/Shared/Error" /> 翻阅一些大神写的博客,在他们的博客中指出defaultRedirect是指向错误页面的URL,可是经过本人测试的时候,发现

Django(三) ORM 数据库操作

比较有用 转自 http://blog.csdn.net/fgf00/article/details/53678205 一.DjangoORM 创建基本类型及生成数据库表结构 1.简介 2.创建数据库 表结构 二.Django ORM基本增删改查 1.表数据增删改查 2.表结构修改 三.Django ORM 字段类型 1.字段类型介绍 2.字段参数介绍 3.Django ORM 外键操作 一.DjangoORM 创建基本类型及生成数据库表结构 1.简介 ORM:关系对象映射.定义一个类自动生成数

微信 小程序布局 左右三区块

/* 3三区块部分 *************/ .wear-diamonds{ display: flex; width: 100%; height: 300rpx; } .wear-diamonds>view{ width: 50%; height:100%; border: 1px solid black; } .diamonds-right>view{ width: 100%; height: 50%; border: 1px solid #000; } //-------------

Redis实战(三)Redis主从复制

从架构 1.主从架构图 2.通过命令 mkdir redisCluster创建redis集群文件夹 3.通过命令mkdir 6380   mkdir 6381   mkdir 6382在redisCluster文件夹下创建三个文件夹 4.通过以下命令将redis.conf分别拷贝到6380.6381. 6382文件夹下 cp /usr/local/redis/redis-3.0.2/redis.conf  ./6380 cp /usr/local/redis/redis-3.0.2/redis.

算法 排序lowB三人组 冒泡排序 选择排序 插入排序

参考博客:基于python的七种经典排序算法   [经典排序算法][集锦]     经典排序算法及python实现 首先明确,算法的实质 是 列表排序.具体就是操作的列表,将无序列表变成有序列表! 一.排序的基本概念和分类 所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作.排序算法,就是如何使得记录按照要求排列的方法. 排序的稳定性: 经过某种排序后,如果两个记录序号同等,且两者在原无序记录中的先后秩序依然保持不变,则称所使用的排序方法是稳定的,反之是不稳定