集合(四_3)Map_ ConcurrentHashMap源码的学习

参考链接:https://www.javaroad.cn/articles/221

                 https://zhuanlan.zhihu.com/p/39535461

 

ConcurrentHashMap(jdk1.8)
    一、前言
        (一)、概述
        (二)、CAS
            1、概述
                CAS(Compare and Swap)比较并转换。\n该算法涉及三个数:内存值V,旧的预期值A,新的预期值B。\n当且仅当旧的预期值A和内存值V相同时,将内存值改为B,否则什么也不做。
                CAS自旋:顾名思义,自己旋转。不断循环,重新计算操作和检测,直到旧的期望值A等于内存值V为止
            2、事例
                比如:\n本来我们在卖东西,有10件存货,那么我们卖了7件,还剩3件需要更新内存,那么\nupdate table set all = '3' where goodsid = #{goodsid}\n但是另一个线程卖了8件,应该库存不足了,但是程序是不是会直接判断成还剩下2件呢?答案是肯定的,这就是一个并发的事件。\n那么CAS怎么做的呢?他在set的时候先对比一下库存是不是发生了变化:\nupdate table set all = '2' where goodsid = #{goodsid} and oldall = '10'
            3、CAS和synchronized适用场景
                1、对于资源竞争较少的情况,使用synchronized同步锁进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗cpu资源;\n而CAS基于硬件实现,不需要进入内核,不需要切换线程,操作自旋几率较少,因此可以获得更高的性能。
                2、对于资源竞争严重的情况,CAS自旋的概率会比较大,从而浪费更多的CPU资源,效率低于synchronized。
        (三)、volatile关键字
            1、可见性
                它必须确保释放锁之前对共享数据作出的更改对于随后获得该锁的线程是可见的\n——如果没有同步机制提供的这种可见性保证,线程看到的共享变量可能是修改前的值或不一致的值,\n这将引发许多严重的问题
            2、概述
                volatile变量具有synchronized的可见性,但是并不具备原子特性。\n就是说线程能够自动发现volatile变量的最新值。\nvolatile提供的线程安全非常有限,需要多个变量之间或者某个变量的修改前的值与修改后的值之间没有约束。
            3、正确使用volatile,需要同时满足以下两个条件
                1、对变量的写操作,不可依赖该变量的当前值
                    eg:a++。这看起来类似一个单独操作,而实际上,它是由读取-修改-写入一系列操作组成的组合操作。\n必须1以原子方式执行,而volatile变量,无法实现这一点
                2、该变量没有包含在具有其他变量的不等式中。
                    如果作为判断依据的不等式有两个变量,同时为这两个变量赋值。\n那么为这两个变量赋值的同时,拿到的都是另一个变量的未赋值前的数据,以这种数据进行判断,也许可以通过。\n但是以赋值之后的另一个变量进行判断,也许是应该不通过才对。\n然而事实上却因为取了之前的值而被判断通过,使得两个变量设置进入了错误的值。
    二、内部结构
        (一)结构图
            分支主题
        (二)域
            1、transient volatile Node<K, V>[] table
                散列表,迭代器就是迭代它们了
            2、private transient volatile Node<K, V>[] nextTable
                下一张表,除了在扩容的时候,其余时间都为null
            3、private transient volatile long baseCount;
                基础计数器,通过CAS来更新
            4、private transient volatile int sizeCtl;
                这是代表他的长度(比较复杂,不准备详细讲)
                0:默认值
                -1:代表哈希表正在进行初始化
                大于0:相当于 HashMap 中的 threshold,表示阈值
                小于-1:代表有多个线程正在进行扩容。线程个数为n-1
            5、private transient volatile int transferIndex;
                分割表时使用的索引值
            6、private transient volatile int cellsBusy;
                计算size相关
            7、private transient volatile CounterCell[] counterCells;
                计算size相关
        (三)构造方法
            所有构造
            tableSizeFor() 方法
                这与hashmap中一样,因此这个方法用于找到大于等于initialCapacity的最小的2的幂\n(initialCapacity如果就是2的幂,则返回的还是这个数)
                返回的类似hashMap中的threshold,扩容的阈值,赋值给sizeCtl
                代码
                    分支主题
    三、方法
        (一)put方法
            1、概述
                (1)如果表为null,调用initTable()方法,初始化表
                (2)根据key的hash值找到hash数组相应的索引位置,如果为空,那么直接以CAS无锁式向该位置添加一个节点
                (3)如果节点是ForwardingNode类型,去协助扩容
                (4)如果桶节点是普通的节点,那么锁住头节点
                    如果是链表,在该链表的尾部添加节点(遍历过程中发现相同节点,直接替换旧值)
                    如果是红黑树,利用红黑树添加节点
                (5)如果binCount!=0,说明是新增或修改非首节点。如果链接个数>8 ,转成红黑树。\noldVal!=null ,说明是修改而非新增,不需要进行addCount了(扩容检查),直接返回旧值
                (6)CAS更新baseCount,并检查是否需要扩容(只针对添加操作,修改操作已在上步中返回)
            2、代码
                 
            3、细节方法
                (1)initTable 
                    a、概述
                        该方法的核心思想就是,通过判断sizeCtl。只允许一个线程对表进行初始化。\n1、如果自己抢得到资源,利用CAS方法将sizeCtl设置为-1,表示其正在初始化,并执行初始化。\n最后面的sc =n-(n>>2) 等效为:n-n/4,等效为:0.75*n。\n2、如果未能抢到资源(sc已经是负数来),执行yield()方法让当前正在运行的线程回到可运行状态,以允许具有相同优先级的其他线程获得运行的机会。\n因此,使用yield()的目的是让具有相同优先级的线程之间能够适当的轮换执行。\n但是,实际中无法保证yield()达到让步的目的,因为,让步的线程可能被线程调度程序再次选中。\n无论线程被谁拿到都能带到目的:保证了表同时只会被一个线程初始化
                    b、代码
                         
                (2)ForwardingNode 节点类型
                    a、概述
                        该节点保存了一个nextTable的引用,它指向了一张hash表。\n在扩容操作中,我们需要对每个桶中的结点进行分离和转移,如果某个桶结点中所有节点都已经迁移完成了(已经被转移到新表 nextTable 中了),\n那么会在原 table 表的该位置挂上一个 ForwardingNode 结点,说明此桶已经完成迁移。
                        它包含一个nextTable指针,用于指向下一张表。而且这个节点的key value next指针全部为null,它的hash值为-1.
                        ForwardingNode 继承自 Node 结点,并且它唯一的构造函数将构建一个键,值,next 都为 null 的结点,反正它就是个标识,无需那些属性。但是 hash 值却为 MOVED
                        这里面定义的find的方法是从nextTable里进行查询节点,而不是以自身为头节点进行查找
                        所以,我们在 putVal 方法中遍历整个 hash 表的桶结点,如果遇到 hash 值等于 MOVED,\n说明已经有线程正在扩容 rehash 操作,整体上还未完成,不过我们要插入的桶的位置已经完成了所有节点的迁移。\n由于检测到当前哈希表正在扩容,于是让当前线程去协助扩容
                    b、代码
                         
                (3)helpTransfer
                    a、概述(略)
                    b、代码
                         
                (4)addCount
                    a、概述
                        1、在结束了节点添加之后,要使用CAS的方式来更新baseCount的值。\naddCount 方法在更新 baseCount 失败的时候,\n会调用 fullAddCount 将这些失败的结点包装成一个 CounterCell 对象,保存在 CounterCell 数组中\n2、我们还要判断是否已经达到了他的阈值,判断是否需要扩容
                    b、代码
                        
                         
        (二)get方法
            a、概述
                get方法是不用加锁的,是非阻塞的。因为concurentHashMap的node节点是重写过的,\n在val,和next上加上了volatile关键字,那么它每次获取的都是最新的值
            b、代码
                next 和 val设置来volatile
                get()
        (三)clear方法
            a、概述
                clear 方法将删除整张哈希表中所有的键值对,删除操作也是一个桶一个桶的进行删除。
            b、代码
                clear()
        (四)size方法
            a、概述
                size 方法的作用是为我们返回哈希表中实际存在的键值对的总数
                在sumCount()方法中,返回的sum并不只是baseCount还有CounterCell的值,\n之所以没有直接返回baseCont(基础计数),是因为我们的 addCount 方法用于 CAS 更新 baseCount,\n但很有可能在高并发的情况下,更新失败,那么这些节点虽然已经被添加到哈希表中了,但是数量却没有被统计
                还好,addCount 方法在更新 baseCount 失败的时候,\n会调用 fullAddCount 将这些失败的结点包装成一个 CounterCell 对象,\n保存在 CounterCell 数组中。\n那么整张表实际的 size 其实是 baseCount 加上 CounterCell 数组中元素的个数。
            b、代码
                size()
                sumCount
    四、总结
        ConcurrentHashMap的key和Value都不能为null
        底层结构是散列表(数组+链表)+红黑树,这一点和HashMap是一样的。
        Hashtable是将所有的方法进行同步,效率低下。\n而ConcurrentHashMap作为一个高并发的容器,它是通过部分锁定+CAS算法来进行实现线程安全的。\nCAS算法也可以认为是乐观锁的一种~
        在高并发环境下,统计数据(计算size...等等)其实是无意义的,因为在下一时刻size值就变化了。
        get方法是非阻塞,无锁的。重写Node类,通过volatile修饰next来实现每次获取都是最新设置的值
    五、参考链接
        https://www.javaroad.cn/articles/221
        https://zhuanlan.zhihu.com/p/39535461
 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
旅游社交小程序功能有管理员和用户。管理员有个人中心,用户管理,每日签到管理,景点推荐管理,景点分类管理,防疫查询管理,美食推荐管理,酒店推荐管理,周边推荐管理,分享圈管理,我的收藏管理,系统管理。用户可以在微信小程序上注册登录,进行每日签到,防疫查询,可以在分享圈里面进行分享自己想要分享的内容,查看和收藏景点以及美食的推荐等操作。因而具有一定的实用性。 本站后台采用Java的SSM框架进行后台管理开发,可以在浏览器上登录进行后台数据方面的管理,MySQL作为本地数据库,微信小程序用到了微信开发者工具,充分保证系统的稳定性。系统具有界面清晰、操作简单,功能齐全的特点,使得旅游社交小程序管理工作系统化、规范化。 管理员可以管理用户信息,可以对用户信息添加修改删除。管理员可以对景点推荐信息进行添加修改删除操作。管理员可以对分享圈信息进行添加,修改,删除操作。管理员可以对美食推荐信息进行添加,修改,删除操作。管理员可以对酒店推荐信息进行添加,修改,删除操作。管理员可以对周边推荐信息进行添加,修改,删除操作。 小程序用户是需要注册才可以进行登录的,登录后在首页可以查看相关信息,并且下面导航可以点击到其他功能模块。在小程序里点击我的,会出现关于我的界面,在这里可以修改个人信息,以及可以点击其他功能模块。用户想要把一些信息分享到分享圈的时候,可以点击新增,然后输入自己想要分享的信息就可以进行分享圈的操作。用户可以在景点推荐里面进行收藏和评论等操作。用户可以在美食推荐模块搜索和查看美食推荐的相关信息。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值