ConcurrentHashMa源码

概括:

        这个哈希表的主要设计目标是维护并发可读性(通常方法get(),但也迭代器和相关方法),同时最小化更新争用。

        次要的目标是保持空间消耗与java.util相同或更好。

        HashMap,并支持high多个线程对空表的初始插入率。

 

        这个映射通常充当一个binned (bucked)哈希表。每一个键值映射保存在节点中。大多数节点都是实例包含散列、键、值和next的基本节点类字段。

然而,存在各种子类:treenode排列成平衡的树,而不是列表。树冠支撑着树根一组树状阳极。转发节点位于头部调整大小期间的桶。reservationnode用作

占位符,同时在computeIfAbsent和相关的方法。类型为TreeBin、forwarding节点和ReservationNode不保存正常的用户密钥、值或散列,以及在搜索过程中容易区分等。因为它们有负哈希字段和空键和值字段。(这些特殊的节点不是不常见就是短暂的,因此,携带一些未使用的字段的影响是微不足道。)

 

        时,将该表惰性地初始化为2的幂级大小第一次插入。表中的每个bin通常包含一个节点列表(通常,列表只有0个或一个节点)。表访问需要volatile/原子读、写和案件。因为没有其他方法来安排这个添加进一步的间接,我们使用了intrinsics (sun.misc.Unsafe)操作。

 

        我们使用节点哈希字段的顶部(符号)位进行控制目的——由于寻址,无论如何它都是可用的约束。具有负哈希字段的节点是特殊的在map方法中处理或忽略。

 

        方法中第一个节点的插入(通过put或其变体)空桶是通过将其装箱到桶中来执行的。这是到目前为止,put操作在most下最常见的情况键/散列分布。其他更新操作(插入、删除和替换)需要锁。我们不想浪费将一个不同的锁对象与之关联所需的空间每个bin,因此使用bin列表本身的第一个节点作为一个锁。对这些锁的锁定支持依赖于内置监控“同步”。

        将列表的第一个节点用作锁本身并不会这样做足够了:当一个节点被锁定时,任何更新都必须首先进行确认它仍然是锁定后的第一个节点,并且如果没有,请重试。因为新节点总是附加到列表中,一旦一个节点在一个bin中第一个被删除,它就会保持在第一个直到被删除或bin(调整大小后)失效。

 

每个bin锁的主要缺点是其他更新在受相同保护的bin列表中的其他节点上执行操作lock可能会停止,例如当user =()或映射时功能需要很长时间。然而,据统计,

随机哈希码,这不是一个常见的问题。理想情况下,箱中节点的频率服从泊松分布(http://en.wikipedia.org/wiki/Poisson_distribution)给定调整阈值,参数平均约为0.5为0.75,但由于调整大小,方差较大粒度。忽略方差,期望发生的列表大小k是(exp(-0.5) * pow(0.5, k) / factorial(k))。的

第一个值是:

0:    0.60653066
1:    0.30326533

2:    0.07581633
3:    0.01263606
4:    0.00157952
5:    0.00015795
6:    0.00001316
7:    0.00000094
8:    0.00000006

多于:少于千分之一

两个访问不同的线程的锁争用概率元素大约是随机哈希下的1 /(8 * #元素)。

        实际中遇到的哈希代码分布有时明显偏离统一的随机性。这个包括n>时的情况(1<<30),因此某些键必须碰撞。同样地,对于多个键都是设计为具有相同的哈希代码或仅不同的哈希代码隐藏的高位。所以我们使用了一个二级策略当bin中的节点数超过阈值。这些树键使用平衡树来保存节点(a特殊形式的红黑树),边界搜索时间O(log n)。treebin中的每个搜索步骤至少是和普通的列表一样慢,但考虑到n不能超过(1<<64)(在地址用完之前)此边界搜索步进、锁定保持时间等,达到合理的常数(大致每个操作检查100个节点(最坏情况),只要键具有可比性(非常常见——字符串、长字符串等)。treebin节点(treenodes)也保持相同的“next”遍历指针作为常规节点,因此可以遍历以同样的方式使用迭代器。

当占用率超过某个百分比时,将调整表的大小。阈值(名义上为0.75,但见下文)。任何线程注意到垃圾箱过满可能有助于在启动线程分配并设置替换数组。但是,这些其他线程可能会继续运行,而不是停止运行。插入等。使用treebins保护我们不受调整大小时过度填充的最坏情况影响进步。通过一个接一个地传送垃圾箱来调整收益大小,从桌子到下一张桌子。然而,线程声称很小之前要传输的索引块(通过字段transferIndex)这样做可以减少争用。田间的世代印记sizectl确保大小调整不会重叠。因为我们是使用两次扩展的力量,每个箱子的元件必须要么保持同一索引,要么以2的幂移动偏移量。我们通过捕获旧节点可以重用的情况,因为它们的下一个字段不会改变的。平均来说,只有大约六分之一的人需要当表翻倍时进行克隆。它们替换的节点将是一旦不再被引用,便可收集的垃圾任何可能同时处于遍历表。在传输时,旧的表bin包含只有一个特殊的转发节点(哈希字段为“moved”)。包含下一个表作为其键。遇到一个转发节点,访问和更新操作重新启动,使用新桌子。

转载于:https://my.oschina.net/u/3358860/blog/3058338

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值