ConcurrentHashMap1.8 – 扩容详解「建议收藏」

您所在的位置:网站首页 hashmap在扩容的时候会线程阻塞吗 ConcurrentHashMap1.8 – 扩容详解「建议收藏」

ConcurrentHashMap1.8 – 扩容详解「建议收藏」

2024-06-15 06:09| 来源: 网络整理| 查看: 265

大家好,又见面了,我是你们的朋友全栈君。

简介

ConcurrenHashMap 在扩容过程中主要使用 sizeCtl 和 transferIndex 这两个属性来协调多线程之间的并发操作,并且在扩容过程中大部分数据依旧可以做到访问不阻塞,具体是如何实现的,请继续 。

说明:该源码来自于 jdk_1.8.0_162 版本 。

特别说明:不想看源码可直接跳到后面直接看图解 。

一、sizeCtl 属性在各个阶段的作用

(1)、新建而未初始化时

代码语言:javascript复制int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY : tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1)); this.sizeCtl = cap;

作用:sizeCtl 用于记录初始容量大小,仅用于记录集合在实际创建时应该使用的大小的作用 。

(2)、初始化过程中

代码语言:javascript复制U.compareAndSwapInt(this, SIZECTL, sc, -1)

作用:将 sizeCtl 值设置为 -1 表示集合正在初始化中,其他线程发现该值为 -1 时会让出CPU资源以便初始化操作尽快完成 。

(3)、初始化完成后

代码语言:javascript复制Node[] nt = (Node[])new Node[n]; table = tab = nt; sc = n - (n >>> 2); sizeCtl = sc;

作用:sizeCtl 用于记录当前集合的负载容量值,也就是触发集合扩容的极限值 。

(4)、正在扩容时

代码语言:javascript复制//第一条扩容线程设置的某个特定基数 U.compareAndSwapInt(this, SIZECTL, sc, (rs = 0) { Node[] tab, nt; int n, sc; //检查当前集合元素个数 s 是否达到扩容阈值 sizeCtl ,扩容时 sizeCtl 为负数,依旧成立,同时还得满足数组非空且数组长度不能大于允许的数组最大长度这两个条件才能继续 //这个 while 循环除了判断是否达到阈值从而进行扩容操作之外还有一个作用就是当一条线程完成自己的迁移任务后,如果集合还在扩容,则会继续循环,继续加入扩容大军,申请后面的迁移任务 while (s >= (long)(sc = sizeCtl) && (tab = table) != null && (n = tab.length) < MAXIMUM_CAPACITY) { int rs = resizeStamp(n); // sc < 0 说明集合正在扩容当中 if (sc < 0) { //判断扩容是否结束或者并发扩容线程数是否已达最大值,如果是的话直接结束while循环 if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 || sc == rs + MAX_RESIZERS || (nt = nextTable) == null || transferIndex RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 || sc == rs + MAX_RESIZERS || transferIndex = (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY : tableSizeFor(size + (size >>> 1) + 1); int sc; //如果不满足条件,也就是 sizeCtl < 0 ,说明有其他线程正在扩容当中,这里也就不需要自己去扩容了,结束该方法 while ((sc = sizeCtl) >= 0) { Node[] tab = table; int n; //如果数组初始化则进行初始化,这个选项主要是为批量插入操作方法 putAll 提供的 if (tab == null || (n = tab.length) == 0) { n = (sc > c) ? sc : c; //初始化时将 sizeCtl 设置为 -1 ,保证单线程初始化 if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { try { if (table == tab) { @SuppressWarnings("unchecked") Node[] nt = (Node[])new Node[n]; table = nt; sc = n - (n >>> 2); } } finally { //初始化完成后 sizeCtl 用于记录当前集合的负载容量值,也就是触发集合扩容的阈值 sizeCtl = sc; } } } else if (c = MAXIMUM_CAPACITY) break; //插入节点后发现链表长度达到8个或以上,但数组长度为64以下时触发的扩容会进入到下面这个 else if 分支 else if (tab == table) { int rs = resizeStamp(n); //下面的内容基本跟上面 addCount 方法的 while 循环内部一致,可以参考上面的注释 if (sc < 0) { Node[] nt; if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 || sc == rs + MAX_RESIZERS || (nt = nextTable) == null || transferIndex >> 3) / NCPU : n) < MIN_TRANSFER_STRIDE) stride = MIN_TRANSFER_STRIDE; // subdivide range if (nextTab == null) { // 初始化新数组(原数组长度的2倍) try { @SuppressWarnings("unchecked") Node[] nt = (Node[])new Node[n = bound || finishing) advance = false; else if ((nextIndex = transferIndex) = n || i + n >= nextn) { int sc; //扩容结束后做后续工作,将 nextTable 设置为 null,表示扩容已结束,将 table 指向新数组,sizeCtl 设置为扩容阈值 if (finishing) { nextTable = null; table = nextTab; sizeCtl = (n >> 1); return; } //每当一条线程扩容结束就会更新一次 sizeCtl 的值,进行减 1 操作 if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) { //(sc - 2) != resizeStamp(n)


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3