zoukankan      html  css  js  c++  java
  • 探究HashMap线性不安全(三)——死循环的产生

    内容

      网上很多资料都详细地讲解了HashMap底层的实现,但是讲到HashMap的并发操作不是线性安全时,往往一笔带过:在多个线程并发扩容时,会在执行transfer()方法转移键值对时,造成链表成环,导致程序在执行get操作时形成死循环

    ​  对于没有研究过该过程的童鞋,很难费解这句话的含义。下面笔者分四个小节带着大家共同研究一下JDK1.7和JDK1.8版本下HashMap的线性不安全是怎么造成的,详细探究链表成环的形成过程。如果对于HashMap底层的put、get操作不清楚,建议先学习参考1中的内容。

    适合人群

    ​  Java进阶

    说明

      转载请说明出处:探究HashMap线性不安全(三)——死循环的产生

    参考

    ​ 1、https://www.toutiao.com/i6544826418210013700/ HashMap底层数据结构原理

    ​ 2、https://www.toutiao.com/i6545790064104833539/ 为什么HashMap非线程安全

    ​ 3、https://blog.csdn.net/qq_32182461/article/details/81152025 hashmap并发情况下的成环原因(笔者认为该文是一种误解)

    正文

    ​  本节将探究环形链表是如何在hashMap查询时产生死循环的。

    ​  以上节为例,当hashMap对象查找一个不为空的Key时,会执行getEntry(key)方法。

    1 public V get(Object key) {
    2         //key为null时,从index为0的链表中查找数据
    3         if (key == null)
    4             return getForNullKey();
    5         //key不为null是查找数据    
    6         Entry<K,V> entry = getEntry(key);
    7         //如果未查找到key对应的值,那么entry为null,get方法会返回null。
    8         return null == entry ? null : entry.getValue();
    9     }

    1538216304142

    ​  getEntry(key)方法会计算Key的hash值,然后通过indexFor(hash, table.length)对hash值取模求得key对应table数组的下标index。如果这个Key对应的table数组下标为3,那么线程会在下标3处的环形链表上遍历检索目标key对应的值。当key对应的键值对不存在,线程将进入死循环,代码如下所示:

     1 final Entry<K,V> getEntry(Object key) {
     2     //计算key的hash值
     3     int hash = (key == null) ? 0 : hash(key);
     4     //通过indexFor(hash, table.length)求得key对应的index,然后遍历index下表的链表
     5     for (Entry<K,V> e = table[indexFor(hash, table.length)];
     6          e != null;
     7          e = e.next) {
     8         Object k;
     9         //如果链表中不存在对应key的键值对,且链表为环形,那么当前线程将在for语句中产生死循环。
    10         if (e.hash == hash &&
    11             ((k = e.key) == key || (key != null && key.equals(k))))
    12             return e;
    13     }
    14     return null;
    15 }

      下面写一个​测试方法,使用多线程对HashMap进行并发写操作,并进行单线程读来演示死循环的产生。

     1 public class TestMain {
     2     public static void main(String[] args) throws InterruptedException {
     3         final HashMap<String, String> map = new HashMap<>();
     4         System.out.println("begin");
     5         int num = 15;
     6         final CountDownLatch countDownLatch = new CountDownLatch(num);
     7         //开始并发put操作
     8         for (int i = 0; i < num; i++) {
     9             final Integer integer = i;
    10             new Thread() {
    11                 @Override
    12                 public void run() {
    13                     for (int j = 0; j < 100; j++) {
    14                         map.put("a" + integer + j, "b" + integer + j);
    15                     }
    16                     countDownLatch.countDown();
    17                 }
    18             }.start();
    19         }
    20         //等待所有线程完成写入操作      
    21         countDownLatch.await();
    22         System.out.println(map.size());
    23         //循环读取map中不存在的key,如果生成了环形链表,当key的hash值匹配到该链表时,发生死循环
    24         for (int m = 0; m < 200; m++) {
    25             System.out.println("c" + m + ":  " + map.get("c" + m));
    26         }
    27     }
    28 } 

      截图如下(笔者大概实验了10次,模拟出次故障):

      至此,JDK1.7中HashMap的线性不安全特性已经论证完毕。下一节,将探究JDK1.8中HashMap的线性不安全特性。

  • 相关阅读:
    Python之pytest 基础
    unittest和pytest的区别
    Selenium 常用定位对象元素的方法
    ORCAl存储过程
    Mysql的存储过程
    TestNG 搭建测试框架 自动化测试
    通过junit/TestNG+java 实现自动化测试
    查看APP用到的图片方法
    码农干私活的建议(转)
    Android的onCreateOptionsMenu()创建菜单Menu详解(转)
  • 原文地址:https://www.cnblogs.com/lonelyJay/p/9726195.html
Copyright © 2011-2022 走看看