ConcurrentHashMap是如何实现线程安全的

 

但是又为何需要学习ConcurrentHashMap?用不就完事了?我认为学习其源码有两个好处:

  1. 更灵活的运用ConcurrentHashMap
  2. 欣赏并发编程大师Doug Lea的作品,源码中有很多值得我们学习的并发思想,要意识到,线程安全不仅仅只是加锁
    ConcurrentHashMap是怎么做到线程安全的?

        get方法如何线程安全地获取key、value?
        put方法如何线程安全地设置key、value?
        size方法如果线程安全地获取容器容量?
        底层数据结构扩容时如果保证线程安全?
        初始化数据结构时如果保证线程安全?

    ConcurrentHashMap并发效率是如何提高的?

        和加锁相比较,为什么它比HashTable效率高?
    参考链接:https://blog.csdn.net/qq_41737716/article/details/90549847
                            
    程序中的可伸缩性(提升外部资源即可提升并发性能的比率)是由程序中串行执行部分所影响的,而常见的串行执行有锁竞争(上下文切换消耗、等待、串行)等等,这给了我们一个启发,可以通过减少锁竞争来优化并发性能,而ConcurrentHashMap则使用了锁分段(减小锁范围)、CAS(乐观锁,减小上下文切换开销,无阻塞)等等技

    相关概念

    Amdahl定律

    此节定律描述均来自《Java并发编程实战》一书

     

    假设F是必须被串行执行的部分,N代表处理器数量,Speedup代表加速比,可以简单理解为CPU使用率
    在这里插入图片描述
    此公式告诉我们,当N趋近无限大,加速比最大趋近于1/F;

    N越大,处理器越多,串行执行越少,cpu利用率越大
    初始化数据结构时的线程安全

    HashMap的底层数据结构这里简单带过一下,不做过多赘述:
    在这里插入图片描述
    大致是以一个Node对象数组来存放数据,Hash冲突时会形成Node链表,在链表长度超过8,Node数组超过64时会将链表结构转换为红黑树,Node对象:

     


    static class Node<K,V> implements Map.Entry<K,V> {
      final int hash;
      final K key;
      volatile V val;
      volatile Node<K,V> next;
      ...
    }

     

     总结

    就算有多个线程同时进行put操作,在初始化数组时使用了乐观锁CAS操作来决定到底是哪个线程有资格进行初始化,其他线程均只能等待。

    用到的并发技巧:

        volatile变量(sizeCtl):它是一个标记位,用来告诉其他线程这个坑位有没有人在,其线程间的可见性由volatile保证。
        CAS操作:CAS操作保证了设置sizeCtl标记位的原子性,保证了只有一个线程能设置成功

     

     

     

     由于其减小了锁的粒度,若Hash完美不冲突的情况下,可同时支持n个线程同时put操作,n为Node数组大小,在默认大小16下,可以支持最大同时16个线程无竞争同时操作且线程安全。当hash冲突严重时,Node链表越来越长,将导致严重的锁竞争,此时会进行扩容,将Node进行再散列,下面会介绍扩容的线程安全性。总结一下用到的并发技巧:

        减小锁粒度:将Node链表的头节点作为锁,若在默认大小16情况下,将有16把锁,大大减小了锁竞争(上下文切换),就像开头所说,将串行的部分最大化缩小,在理想情况下线程的put操作都为并行操作。同时直接锁住头节点,保证了线程安全
        Unsafe的getObjectVolatile方法:此方法确保获取到的值为最新

    扩容操作的线程安全

    在扩容时,ConcurrentHashMap支持多线程并发扩容,在扩容过程中同时支持get查数据,若有线程put数据,还会帮助一起扩容,这种无阻塞算法,将并行最大化的设计,堪称一绝。

    先来看看扩容代码实现:

    ConcurrentHashMap运用各类CAS操作,将扩容操作的并发性能实现最大化,在扩容过程中,就算有线程调用get查询方法,也可以安全的查询数据,若有线程进行put操作,还会协助扩容,利用sizeCtl标记位和各种volatile变量进行CAS操作达到多线程之间的通信、协助,在迁移过程中只锁一个Node节点,即保证了线程安全,又提高了并发性能。


    参考链接:https://blog.csdn.net/qq_41737716/article/details/90549847

posted @ 2024-02-01 16:40  予真  阅读(27)  评论(0编辑  收藏  举报