并发编程(13):HashMap的线程安全问题分析以及ConcurrentHashMap的实现原理

1、JDK1.7的HashMap:

       1.1、数据结构哈希表 + 链表

                                       

          1.2、解决hash冲突的方式就是链址法,就是通过key计算的index重复的话,就使用头插法加入链表。

          1.3、特殊key值处理,什么是特使key,也就是key是null,其结论如下:

                      HashMap中,是允许key、value都为null的,且key为null只存一份,多次存储会将旧value值覆盖;
                     Ⅱ key为null的存储位置,都统一放在下标为0的bucket,即:table[0]位置的链表;
                     Ⅲ 如果是第一次对key=null做put操作,将会在table[0]的位置新增一个Entry结点,使用头插法做链表插入。

          1.4、扩容,当哈希表中存放的k-v对数量超过了当前阈值(threshold = table.length * loadFactor),且当前的bucket下标有链表存在,那么就做扩容处理(resize)。扩容后,重新计算hash,最终得到新的bucket下标,然后使用头插法新增结点。

                  扩容的两大核心就是:

                    Ⅰ扩容后大小是扩容前的2倍,一般指的是哈希表的长度;

                     Ⅱ扩容后,重新计算hash,最终得到新的bucket下标,然后使用头插法新增结点。

           1.5、线程安全问题;

                    Ⅰ多线程同时做写入操作会导致数据覆盖:当线程A和线程B都获取到了bucket的头结点后,若此时线程A的时间片用完,线程B将其新数据完成了头插法操作,此时轮到线程A操作,但这时线程A所据有的旧头结点已经过时了(并未包含线程B刚插入的新结点),线程A再做头插法操作,就会抹掉B刚刚新增的结点,导致数据丢失。

                    Ⅱ扩容的时候导致死循环:演示如下;

                              1、假设当前的HashMap的数据如下:

                                          

                              2、线程A和B各自新增了一个新的哈希table,在线程A已做完扩容操作后,线程B才开始扩容。

                                          

                                          当线程B完成将元素a做完头插法后:

                                          

                                           线程B开始处理元素b了,由于这个时候线程A已经将b的next改为了a,头插法插入b情况如下:

                                               

                                            由于此时对于线程B来说,next是又是a元素了,a元素的next是null,那么线程B又会再次头插法a:

                                              

                                                这就形成了环链,而且造成了数据c的丢失。

 

2、JDK1.8的HashMap:

          2.1、数据结构哈希表 + 链表(或红黑树):

                      

          2.2、 解决hash冲突的方式就是链址法,就是通过key计算的index重复的话,就使用尾插法加入链表,在JDK7中,新增结点是使用头插法,但在JDK8中,在链表使用尾插法,将待新增结点追加到链表末尾。

          2.3、当链表的长度达到默认的长度8后就会转为红黑树来提供查询效率。

          2.4、扩容,当哈希表中存放的k-v对数量超过了当前阈值(threshold = table.length * loadFactor),且当前的bucket下标有链表存在,那么就做扩容处理(resize)。扩容后,重新计算hash,最终得到新的bucket下标,然后使用尾插法新增结点。

          2.5、线程安全问题:

                   Ⅰ多线程同时做写入操作会导致数据覆盖:当线程A和线程B都获取到了bucket的头结点后,若此时线程A的时间片用完,线程B将其新数据完成了头插法操作,此时轮到线程A操作,但这时线程A所据有的旧头结点已经过时了(并未包含线程B刚插入的新结点),线程A再做头插法操作,就会抹掉B刚刚新增的结点,导致数据丢失。

                    Ⅱ由于JDK1.8中是采用尾插法,因此不会出现环链的问题。

 

3、ConcurrentHasnMap实现原理:

      观看了蛮多ConcurrentHasnMap实现原理的博客,这一篇讲得挺好,在此借用一下,如有版权问题请联系我修改。

      https://blog.csdn.net/ddxd0406/article/details/81389583

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值