ConCurrentHashMap在JDK7和JDK8对比(结构、初始化、Size和添加元素)

推荐阅读:https://www.jianshu.com/p/e694f1e868ec

0.特点

ConcurrentHashMap在java.util.Concurrent包下,最大的特点是安全、性能较同样安全的HashTable更高。

它的设计与实现非常精巧,大量的利用了volatile,final,CAS等lock-free技术来减少锁竞争对于性能的影响。

ConcurrentHashMap中的HashEntry相对于HashMap中的Entry有一定的差异性:HashEntry中的value以及next都被volatile修饰,这样在多线程读写过程中能够保持它们的可见性。

1.结构

JDK7:SegmentReentrantLock可重入锁)+Entry、链表(Hash表

JDK8:CAS+Synchronized+数组、链表、红黑树(Node

结构图

JDK1.7:

 

JDK1.8:

其实可以看出JDK1.8版本的ConcurrentHashMap的数据结构已经接近HashMap。

1.数据结构更精巧:取消了Segment分段锁的数据结构,取而代之的是数组+链表+红黑树的结构。
2.锁的方式更简单:JDK1.7采用segment的分段锁机制实现线程安全,其中segment继承自ReentrantLock。JDK1.8采用CAS+Synchronized保证线程安全。
3.锁的粒度变细:原来是对需要进行数据操作的Segment加锁,现调整为对每个数组元素加锁(Node)。
4.链表转化为红黑树:定位结点的hash算法简化会带来弊端,Hash冲突加剧,因此在链表节点数量大于8时,会将链表转化为红黑树进行存储。
5.查询时间复杂度变小:从原来的遍历链表O(n),变成遍历红黑树O(logN)。

2.初始化

JDK7

Segment数组的大小默认为16,Segment中的HashEntry数组大小最小值为2。它们大小都要为2的幂次方。

JDK8

懒加载策略,和HashMap有相似。

在第一次put的时候才会初始化,Node数组默认大小为16

下面是源代码中的一些默认参数,可以看出和HashMap很相似了。

private static final int DEFAULT_CAPACITY = 16;

static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

private static final int MAXIMUM_CAPACITY = 1 << 30;

private static final int DEFAULT_CONCURRENCY_LEVEL = 16;

private static final float LOAD_FACTOR = 0.75f;

static final int TREEIFY_THRESHOLD = 8;

static final int UNTREEIFY_THRESHOLD = 6;

static final int MIN_TREEIFY_CAPACITY = 64;

static final int MOVED     = -1; // hash for forwarding nodes
static final int TREEBIN   = -2; // hash for roots of trees
static final int RESERVED  = -3; // hash for transient reservations
static final int HASH_BITS = 0x7fffffff;

3.添加元素

JDK7

1.计算Segment数组的相应位置,如果还没有初始化,那就CAS赋值,执行Segment的put插入数据。

2.如果执行Segment对象时,已经有线程获取锁,那么该线程就重复执行tryLock()获取锁,最多重复64次,超过次数就挂起该线程。

每个线程执行插入操作之前要获取锁,执行完要释放锁,并且唤醒等待该Segment对象的其他线程。

JDK8

1.如果该位置的Node还未被初始化,那就CAS插入相关数据。

2.如果该位置已经初始化,那就加Synchronized锁,插入(判断结构是链表还是红黑树,按对应方式插入)

3.更新个数,检查是否需要树化。

4.计算个数

JDK7

因为总个数=每个Segment中的个数相加,但是这是不断变化的,所以采用如下方法:

1.先不加锁,连续计算3次总的个数。

2.如果前后两次结果计算相同,那么这就是准确的,返回即可。

3.如果都不同,则给每个Segment进行加锁,再计算个数。

JDK8

使用一个volatile修饰的变量记录元素个数,当插入/删除数据时,会更新个数。

元素个数保存在baseCount中,部分元素个数变化保存在CountCell中,通过累加CountCell和baseCount的数量即可算出总个数。

1.并发很高时,使用CountCell记录元素个数的变化。

2.如果CountCell数组的CountCells为空,那就初始化,并CAS插入对应数据。

3.如果插入失败,尝试CAS修改baseCount字段,若成功就退出,不成功就继续CAS插入CountCell。

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值