ConcurrentHashMap1.7和1.8对比
发布日期:2021-08-14 18:04:32 浏览次数:4 分类:技术文章

本文共 5026 字,大约阅读时间需要 16 分钟。

ConcurrentHashMap1.7和1.8对比

数据结构

1.7中采用Segment+HashEntry的方式实现

ConcurrentHashMap初始化时,计算出Segment数组的大小ssize和每个SegmentHashEntry数组的大小cap,并初始化Segment数组的第一个元素;

其中ssize大小为2的幂次方默认为16cap大小也是2的幂次方最小值为2,最终结果根据初始化容量initialCapacity进行计算,计算过程如下

if (c * ssize < initialCapacity)    ++c;int cap = MIN_SEGMENT_TABLE_CAPACITY;while (cap < c)    cap <<= 1;

因为Segment继承了ReentrantLock,所有segment是线程安全的

1.8中放弃了Segment分段锁的设计,使用的是Node+CAS+Synchronized来保证线程安全性

只有在第一次执行put方法是才会初始化Node数组

 

put操作

1.7 put

当执行put方法插入数据的时候,根据key的hash值,在Segment数组中找到对应的位置

如果当前位置没有值,则通过CAS进行赋值,接着执行Segmentput方法通过加锁机制插入数据

假如有线程AB同时执行相同Segmentput方法

线程A 执行tryLock方法成功获取锁,然后把HashEntry对象插入到相应位置

线程B 尝试获取锁失败,则执行scanAndLockForPut()方法,通过重复执行tryLock()方法尝试获取锁

多处理器环境重复64次,单处理器环境重复1次,当执行tryLock()方法的次数超过上限时,则执行lock()方法挂起线程B

当线程A执行完插入操作时,会通过unlock方法施放锁,接着唤醒线程B继续执行

1.8 put

当执行put方法插入数据的时候,根据key的hash值在Node数组中找到相应的位置

如果当前位置的Node还没有初始化,则通过CAS插入数据

else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {    //如果当前位置的`Node`还没有初始化,则通过CAS插入数据    if (casTabAt(tab, i, null, new Node
(hash, key, value, null))) break; // no lock when adding to empty bin}

 

如果当前位置的Node已经有值,则对该节点加synchronized锁,然后从该节点开始遍历,直到插入新的节点或者更新新的节点

if (fh >= 0) {    binCount = 1;    for (Node
e = f;; ++binCount) { K ek; if (e.hash == hash && ((ek = e.key) == key || (ek != null && key.equals(ek)))) { oldVal = e.val; if (!onlyIfAbsent) e.val = value; break; } Node
pred = e; if ((e = e.next) == null) { pred.next = new Node
(hash, key, value, null); break; } }}

 

如果当前节点是TreeBin类型,说明该节点下的链表已经进化成红黑树结构,则通过putTreeVal方法向红黑树中插入新的节点

else if (f instanceof TreeBin) {
   Node
p; binCount = 2; if ((p = ((TreeBin
)f).putTreeVal(hash, key, value)) != null) { oldVal = p.val; if (!onlyIfAbsent) p.val = value; } }

如果binCount不为0,说明put操作对数据产生了影响,如果当前链表的节点个数达到了8个,则通过treeifyBin方法将链表转化为红黑树

 

size 操作

1.7 size实现

统计每个segment对象中的元素个数,然后进行累加

但是这种方式计算出来的结果不一定准确

因为在计算后面的segment的元素个数时

前面计算过了的segment可能有数据的新增或删除

计算方式为:

先采用不加锁的方式,连续计算两次

如果两次结果相等,说明计算结果准确

如果两次结果不相等,说明计算过程中出现了并发新增或者删除操作

于是给每个segment加锁,然后再次计算

try {    for (;;) {        if (retries++ == RETRIES_BEFORE_LOCK) {            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
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(); }}

 

1.8 size实现

使用一个volatile类型的变量baseCount记录元素的个数

当新增或者删除节点的时候会调用,addCount()更新baseCount

if ((as = counterCells) != null ||    !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {    CounterCell a; long v; int m;    boolean uncontended = true;    if (as == null || (m = as.length - 1) < 0 ||        (a = as[ThreadLocalRandom.getProbe() & m]) == null ||        !(uncontended =          U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {        fullAddCount(x, uncontended);        return;    }    if (check <= 1)        return;    s = sumCount();}

初始化时counterCells为空

在并发量很高时,如果存在两个线程同时执行CAS修改baseCount

则失败的线程会继续执行方法体中的逻辑

使用CounterCell记录元素个数的变化

 

如果CounterCell数组counterCells为空

调用fullAddCount()方法进行初始化

并插入对应的记录数,通过CAS设置cellsBusy字段

只有设置成功的线程才能初始化CounterCell数组

else if (cellsBusy == 0 && counterCells == as &&         U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {    boolean init = false;    try {                           // Initialize table        if (counterCells == as) {            CounterCell[] rs = new CounterCell[2];            rs[h & 1] = new CounterCell(x);            counterCells = rs;            init = true;        }    } finally {        cellsBusy = 0;    }    if (init)        break;}

如果通过CAS设置cellsBusy字段失败的话

则继续尝试通过CAS修改baseCount字段

如果修改baseCount字段成功的话,就退出循环

else if (U.compareAndSwapLong(this, BASECOUNT, v = baseCount, v + x))    break;

否则继续循环插入CounterCell对象;

所以在1.8中的size实现比1.7简单多,因为元素个数保存baseCount中,部分元素的变化个数保存在CounterCell数组中,实现如下:

public int size() {    long n = sumCount();    return ((n < 0L) ? 0 :            (n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE :            (int)n);}​final long sumCount() {    CounterCell[] as = counterCells; CounterCell a;    long sum = baseCount;    if (as != null) {        for (int i = 0; i < as.length; ++i) {            if ((a = as[i]) != null)                sum += a.value;        }    }    return sum;}

通过累加baseCountCounterCell数组中的数量,即可得到元素的总个数;

 

转载于:https://www.cnblogs.com/lezon1995/p/11219555.html

转载地址:https://blog.csdn.net/weixin_30731305/article/details/101226966 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:分布式系统整理
下一篇:Javascript 词法分析

发表评论

最新留言

感谢大佬
[***.8.128.20]2024年12月06日 00时54分14秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

推荐文章

linux清零信号量,求助 :linux 用信号量机制实现重新启动程序 2019-06-17
linux ext2 实验 体会,Linux EXT2文件系统虚拟内存实验设计文献综述.doc 2019-06-17
三个linux命令,必学的几个Linux命令(三) 2019-06-17
c语言的对数怎么编程,在C语言中使用对数函数的方法 2019-06-17
a10改成c语言表达式是,浙江理工大学09-10c语言期末试卷 2019-06-17
气压传感器c语言程序,单片机驱动bmp280气压传感器实现应用源码 2019-06-17
c语言使用定点数代替浮点数计算,浮点数 和 EPSILON (文章越写越啰嗦) 2019-06-17
android mjpg格式,Camera常用格式MJPEG和jpeg-turbo库 2019-06-17
博客园页面定制html代码,博客园页面定制 - osc_97kpb2b5的个人空间 - OSCHINA - 中文开源技术交流社区... 2019-06-17
华北电力大学计算机专业学什么,计算机专业学生装逼入门 2019-06-17
用于标注js脚本的html标签是,js addListener 的封装 用于给标签注册事件 2019-06-17
java更新了ebs系统进不去,Oracle Apache server的服务启动不了,造成进不了EBS系统。请问:如何才能启动Apache?... 2019-06-17
php pdo简书,php mysql PDO 查询操作的实例详解 2019-06-17
劳埃镜 matlab,劳埃德镜 光的干涉现象 2019-06-17
LTI的频域分析matlab,MATLAB与信号实验 —— 连续LTI系统的频域分析 2019-06-17
测试linux防火墙工具,基于Linux的IPv6防火墙测试工具的设计与实现 2019-06-17
axi握手linux过程分析,Linux网络编程之tcpdump抓包分析TCP三次握手过程 2019-06-17
c语言基础编程题文库,C语言基础编程题资料.doc 2019-06-17
到目前为止 linux samba 按转,到目前为止,Linux下最完整的Samba服务器配置攻略 2019-06-17
c 项目调用c语言dll兼容性,c#调用c语言dll需要注意的地方 2019-06-17