currenthashmap扩容原理_ConcurrentHashMap之扩容实现(基于JDK1.8)

本文详细介绍了JDK1.8中ConcurrentHashMap的扩容原理,包括其重要变量如baseCount、sizeCtl的作用,以及在并发扩容时如何控制多个线程参与。在扩容过程中,ConcurrentHashMap采用CAS和synchronized保证线程安全,并在链表长度达到一定阈值时转为红黑树。文章还讲解了扩容过程中节点的转移和线程协作的实现细节。
摘要由CSDN通过智能技术生成

1. 概述

ConcurrentHashMap是JDK提供的一种线程安全的HashMap实现,JDK1.8对ConcurrentHashMap进行了大量优化,除了增加了函数式编程特性,还对加锁方式进行了优化,它抛弃了JDK1.6中分段锁的设计,而是直接对Map中Table数组的每个节点进行加锁,进一步减少了锁粒度,并且不再采用ReentrantLock加锁 ,直接使用synchronized同步块(JDK1.6开始已经对synchronized 做了大量优化,加入了自旋锁、偏向锁、轻量级锁、重量级锁等)。

为了提高查询效率,采用了数组+链表+红黑树的设计,当链表中的元素个数大于64,且数组中链表节点长度大于8,则会自动把链表转化为红黑树,当两个条件有一个不满足时,会回退到数组+单链表的数据结构。

在JDK1.8的实现中,还实现了并发扩容机制,也就是可以由多个线程同时帮助扩容,加速数据转移过程,极大提升了效率,本文尝试着把这个扩容过程解释清楚。

2. 实现原理

2.1 基础

首先介绍一下CocurrentHashMap中几个重要变量,这些变量在原子更新、并发扩容控制以及统计元素个数方面发挥着重要作用。

// 代表Map中元素个数的的基础计数器,当无竞争时直接使用CAS方式更新该数值

transient volatile long baseCount;

/**

* sizeCtl用于table初始化和扩容控制,当为正数时表示table的扩容阈值(n * 0.75),当为负数时表示table正在初始化或者扩容,

* -1表示table正在初始化,其他负值代表正在扩容,第一个扩容的线程会把扩容戳rs左移RESIZE_STAMP_SHIFT(默认16)位再加2更新设置到sizeCtl中(sizeCtl = (rs << 16) + 2),

* 每次一个新线程来扩容时都令sizeCtl = sizeCtl + 1,因此可根据sizeCtl计算出正在扩容的线程数,注释中所

* 描述的 sizeCtl = -(1+threads)是不准确的。扩容时sizeCtl有两部分组成,第一部分是扩容戳,占据sizeCtl的高有效位,长度为

* RESIZE_STAMP_BITS位(默认16),剩下的低有效位长度为32-RESIZE_STAMP_BITS位(16),每个新线程协助扩容时sizeCtl+1

* ,直到所有的低有效位被占满,低有效位默认占16位(最高位为符号位),所以扩容线程数默认最大为65535

*/

transient volatile int sizeCtl;

/**

* 用于控制多个线程去扩容时领取扩容子任务,每个线程领取子任务时,要减去扩容步长,如果能减成功,

* 则成功领取一个扩容子任务,`transferIndex = transferIndex - stride(扩容步长)`,transferIndex减到0时

* 代表没有可以领取的扩容子任务。

*/

transient volatile int transferIndex;

// 扩容或者创建CounterCells时使用的自旋锁(使用CAS实现);

transient volatile int cellsBusy;

/**

* 存储Map中元素的计数器,当并发量较高时`baseCount`竞争较为激烈,更新效率较低,所以把变化的数值

* 更新到`counterCells`中的某个节点上,计算size()时需要统计每个`counterCells`的大小再加上`baseCount`的数值。

*/

transient volatile CounterCell[] counterCells;

/**

* ConcurrentHashMap采用cas算法进行更新变量(table[i],sizeCtl,transferIndex,cellsBusy等)来保证线程安全性,它其实是一种乐观策略,

* 假设每次都不会产生冲突,所以能够直接更新成功,如果出现冲突则再重试,直到更新成功。实现cas主要是借助了`sun.misc.Unsafe`类,该类提供了

* 诸多根据内存偏移量直接从内存中读取设置对象属性的底层操作。

*/

static final sun.misc.Unsafe U;

下面是ConcurrentHashMap中的重要常量的含义及功能说明

// HashMap的最大容量(table数组的长度):2^30,因为hashCode最高两位用于控制目的,因此hashCode最大取值为2^30,

// 所以table数组长度n > hashCode,hashCode & (n-1)时无法索引到数组后面的节点上

private static final int MAXIMUM_CAPACITY = 1 << 30;

// 负载因子,最大容量为数组长度*负载因子,元素个数超过容量则触发扩容

private static final float LOAD_FACTOR = 0.75f;

// 链表长度大于8时链表转化为红黑树

static final int TREEIFY_THRESHOLD = 8;

// 红黑树节点数小于6时红黑树转化为单链表

static final int UNTREEIFY_THRESHOLD = 6;

// 容量大于64时转化为红黑树

static final int MIN_TREEIFY_CAPACITY = 64;

// 最小转移步长:由于在扩容过程中,会把一个待转移的数组分为多个区间段(转移步长),每个线程一次转移一个区间段的数据,

// 一个区间段(转移步长)的默认长度是16,实际运行过程中会动态计算

private static final int MIN_TRANSFER_STRIDE = 16;

/**

* 扩容戳有效位数:每次在需要扩容的时会根据当前数组table的大小生成一个扩容戳,当一个线程需要

* 协助扩容时需要实时计算扩容戳来验证是否

* 需要协助扩容或扩容过程是否完成,生成扩容戳的方式:Integer.numberOfLeadingZeros(n) | (1 << (RESIZE_STAMP_BITS - 1));

* 其中n表示当前t

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值