ConcurrentHashMap 1.7

本文详细解析了JDK1.7中ConcurrentHashMap的数据结构,包括Segment数组、HashEntry和链表的使用,以及1.8版本的数据结构变化。重点介绍了分段锁的实现、并发度、扩容机制的升级,以及1.8中put操作的加锁策略。
摘要由CSDN通过智能技术生成

1.1 数据结构:
在JDK1.7版本中,ConcurrentHashMap的数据结构是由一个Segment数组和多个HashEntry组成,主要实现原理是实现了分段锁的思路解决了多线程的安全问题: Segment 数组 + HashEntry 数组 + 链表,每个Segment中包含一个HashEntry数组,每个HashEntry又是一个链表结构.
在这里插入图片描述

public class ConcurrentHashMap<K, V> extends AbstractMap<K, V> implements ConcurrentMap<K, V>, Serializable {
private static final long serialVersionUID = 7249069246763182397L;

// 分段数组,每一段都是一个 hash 表
final Segment<K,V>[] segments;
}
static final class Segment<K,V> extends ReentrantLock implements Serializable {
// 每段中的表
transient volatile HashEntry<K,V>[] table;
}

1.2 元素查询
二次hash,ConcurrentHashMap 与HashMap和最大的不同在于:put和 get 两次Hash到达指定的HashEntry,第一次Hash定位到Segment,第二次到达Segment里面的HashEntry,然后在遍历HashEntry链表
1.3 ConcurrentHashMap 分段锁的实现
ConcurrentHashMap 对整个桶数组进行了分割分段(Segment),每一把锁只锁容器其中一部分数据,多线程操作不同的分段,就不会存在锁竞争,提高并发访问率。 Segment 本身就继承了 ReentrantLock 具备了锁的功能,在每次 put 前都会先尝试 tryLock() 加锁,如果成功则进行元素存储,如果失败,就会尝试循环加锁
在这里插入图片描述
1.3 ConcurrentHashMap的并发度是什么
ConcurrentHashMap 的并发度就是 segment 的大小,默认为 16 ,这意味着最多同时可以有 16 条线程操作
ConcurrentHashMap ,这也是 ConcurrentHashMap 对 Hashtable 的最大优势
1.4 ConcurrentHashMap扩容机制(jdk1.7)

  1. 1.7版本的ConcurrentHashMap是基于Segment分段锁实现的
  2. 每个Segment相当于㇐个⼩型的HashMap
  3. 每个Segment内部会进⾏扩容,和HashMap的扩容逻辑类似
  4. 先⽣成新的数组,然后转移元素到新数组中
  5. 扩容的判断也是每个Segment内部单独判断的,判断是否超过阈值
    ConcurrentHashMap 1.8
    2.1 数据结构
    1.8中放弃了Segment臃肿的设计,取而代之的是采用用数组+链表+红黑树来实现,利用 CAS + synchronized 来保证并发更新的安全,结构如下:
    在这里插入图片描述
transient volatile Node<K,V>[] table;

/**
* The next table to use; non‐null only while resizing.
*/
private transient volatile Node<K,V>[] nextTable;

2.2 put如何加锁
jdk1.8 的 ConcurrentHashMap 加锁粒度比 jdk1.7 里的 Segment 来加锁粒度更细,并发性能更好。 当执行put方法插入数据时,根据key的hash值,在Node数组中找到相应的位置,实现如下:

  1. 在Node元素中没有值的情况下,则通过CAS插入相应的数据
/ConcurrentHashMap 1018else if ((f = tabAt(tab, i = (n ‐ 1) & hash)) == null) { if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
break;	// no lock when adding to empty bin
}

//ConcurrentHashMap 1027行 synchronized (f) {
if (tabAt(tab, i) == f) {
...
}

  1. 如果在Node节点里面已经存在值的话,那么就使用 synchronized 关键字对元素加锁,再进行之后的 hash
    冲突处理。
    put加锁图解
    在这里插入图片描述
    2.3 扩容安全
    源码分析参考:JDK1.8中的ConcurrentHashMap https://blog.csdn.net/BSSMWYT/article/details/123222347 注意点:
  2. table数组的扩容,一般是新建一个2倍大小的数组,这个过程由一个单线程完成,不允许并发操作;
  3. 数据迁移,可多线程操作。把旧的table各个槽中的结点重新分配到新table中
    具体如下: 1、1.8版本的ConcurrentHashMap不再基于Segment实现 2、 如果某个线程put时,发现没有正在进
    ⾏扩容,则将key-value添加到ConcurrentHashMap中,然后判断是否超过阈值,超过了则进⾏扩容 3、 ConcurrentHashMap是⽀持多个线程同时扩容的 4、当某个线程进⾏put时,如果发现ConcurrentHashMap正在进⾏扩容那么该线程㇐起进⾏扩容
  4. 扩容之前也先⽣成㇐个新的数组
  5. 在转移元素时,先将原数组分组,将每组分给不同的线程来进⾏元素的转移,每个线程负责㇐组或多组的元素转移⼯作
    单线程扩容图解:
    在这里插入图片描述
    多线程协助扩容图解:
    在这里插入图片描述
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值