对ConcurrentHashMap更深一步认识

ConcurrentHashMap是Java中为多线程环境设计的线程安全的哈希映射表,它采用了分段锁技术提高并发性能,对比HashMap,ConcurrentHashMap在数据结构上增加了红黑树和ForwardingNode,以优化扩容和并发操作。与HashTable相比,ConcurrentHashMap避免了全局锁,使用细粒度的锁来提高并发效率。
摘要由CSDN通过智能技术生成

ConcurrentHashMap 的存在意义

我们知道,Java中有了HashMap和HashTable 那为何还需要ConcurrentHashMap?

对比HashMap

ConcurrentHashMap 数据存储结构是:数组+链表+红黑树

HashMap 不是线程安全的,不能在多线程场景使用。

两者相同之处:

  • 数组、链表结构几乎相同,因此底层对数据结构的操作思路是相同的
  • 都实现 Map 接口,多数方法也都是相同的

两者不同之处:

  • 红黑树结构略有不同,HashMap的红黑树节点叫做TreeNode,TreeNode不仅有属性还维护着红黑树的结构,比如查找、新增等;ConcurrentHashMap中红黑树拆分成两块,TreeNode仅维护属性和查找功能, 新增TreeBin来维护红黑树的结构,负责根节点的加锁和解锁
  • ConcurrentHashMap新增ForwardingNode转移节点,扩容会用到,该节点用来保证扩容时线程安全

对比HashTable

  • Hashtable使用synchronized保证线程安全,当一个线程访问其中一个同步方法时,其他线程是不能访问其同步方法(竞争同一把锁)。会导致在put数据时,其他线程也不能get数据之类操作,导致并发效率非常低.
  • ConcurrentHashMap使用分段锁(Segment)技术,将数组分成多段,每个分段锁维护着 HashEntry, 修改数据时先将这一段锁起来,其他线程此时要操作其他分段的数据互不干扰. 操作不是同一分段数据, 线程间不存在竞争关系,大大提高并发效率.

ConcurrentHashMap部分源码分析

数据初始化

/**
 * Initializes table, using the size recorded in sizeCtl.
 */
private final Node<K,V>[] initTable() {
    Node<K,V>[] tab; int sc;
    //1. 通过死循环来保证一定能初始化成功,通过对sizeCtl变量的赋值来保证只被初始化一次
    while ((tab = table) == null || tab.length == 0) {
        //2. sizeCtl小于0,表示有其他线程正在初始化,释放当前CPU的调度权,重新发起锁的竞争,自旋
        if ((sc = sizeCtl) < 0)
            Thread.yield(); // lost initialization race; just spin
        //3. CAS 赋值,保证当前只有一个线程正在初始化,如果第一次初始化这里会将sizeCtl的值赋值为-1,
        //保证了数组的初始化安全性
        else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
            try {
                //4. 可能执行到这里的时候,table已经不为空了,双重check
                if ((tab = table) == null || tab.length == 0) {
                    //5. 初始化数组
                    int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
                    @SuppressWarnings("unchecked")
                    Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
                    table = tab = nt;
                    //>>>:无符号右移,无论是正数还是负数,高位通通补0
                    sc = n - (n >>> 2);
                }
            } finally {
                sizeCtl = sc;
            }
            break;
        }
    }
    return tab;
}

从上面源码看出使用自旋+CAS+双重check来保证数组初始化时的线程安全.

获取数据:Get

ConcurrentHashMap 读取数据比较简单,先获取数组下标,然后通过判断数组下标的 key 是否与目标 key 一致,相等直接返回。如果下标的槽点是链表或红黑树,分别调用相应的查找数据方法,整体思路和 HashMap 很像

public V get(Object key) {
    Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
    //计算hashcode
    int h = spread(key.hashCode());
    //不是空的数组 && 并且当前索引的槽点数据不是空的
    //否则该key对应的值不存在,返回null
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (e = tabAt(tab, (n - 1) & h)) != null) {
        //槽点第一个值和key相等,直接返回
        if ((eh = e.hash) == h) {
            if ((ek = e.key) == key || (ek != null && key.equals(ek)))
                return e.val;
        }
        //如果是红黑树或者转移节点,使用对应的find方法
        else if (eh < 0)
            return (p = e.find(h, key)) != null ? p.val : null;
        //如果是链表,遍历查找
        while ((e = e.next) != null) {
            if (e.hash == h &&
                ((ek = e.key) == key || (ek != null && key.equals(ek))))
                return e.val;
        }
    }
    return null;
}

扩容机制

ConcurrentHashMap 扩容时机是和HashMap相同,都在put方法最后一步检查一下是否需要扩容.但实现上有差别:

  1. 需要把老数组的值全部拷贝到扩容之后的新数组上,先从数组的队尾开始拷贝;
  2. 拷贝数组的槽点时,先把原数组槽点锁住,保证原数组槽点不能操作,成功拷贝到新数组时,把原数组槽点赋值为转移节点;
  3. 若有新数据正好需要put到此槽点,发现槽点为转移节点,会一直等待,所以在扩容完成之前,该槽点对应的数据是不会发生变化;
  4. 从数组的尾部拷贝到头部,每拷贝成功一次,就把原数组中的节点设置为转移节点;
  5. 直到所有数组数据都拷贝到新数组时,直接把新数组赋值给数组容器,拷贝即完成。

最后

如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
在这里插入图片描述
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

全套视频资料:

一、面试合集

在这里插入图片描述
二、源码解析合集
在这里插入图片描述

三、开源框架合集
在这里插入图片描述
欢迎大家一键三连支持,若需要文中资料,直接扫描文末CSDN官方认证微信卡片免费领取↓↓↓

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值