zoukankan      html  css  js  c++  java
  • 并发场景下list的空指针异常和size大小问题

    问题描述:

    对一个源list使用并行流对其进行遍历的时候往宿list添加元素,再次遍历宿list的时候会抛出空指针异常问题而且会现宿list size大小也有问题。

    问题复原:

    @org.junit.Test
    public void test2() {
        List<Integer> source = new LinkedList<>();
        List<String> target = new LinkedList<>();
    
        for (int i = 0; i < 1000; i++) {
            source.add(i);
        }
    
        source.parallelStream().forEach(i -> {
            target.add(String.valueOf(i));
        });
    
        System.out.println("target size -> " + target.size());
    
        for (String s : target) {
            System.out.println(s);
        }
    
    }
    

    运行结果

    image-20211106090035966

    image-20211106090052629

    问题分析:

    不开启多线程的时候targetList的size一定是1000的,而且不会出现空指针异常。

    这两个问题其实都和size++这句话有关

    1. size大小为什么不是1000

    分析源码:

    add是尾插,源码如下

    void linkLast(E e) {
        final Node<E> l = last;
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;
    }
    

    模拟一个场景

    1. size现在的值为100
    2. 线程a拿到了size大小100,此时cpu让出执行权给线程b
    3. 线程b拿到了size大小也为100
    4. 那么无论他们谁先加1,最后size的值会被101覆盖两次,导致size的大小不会是102

    2. 再次遍历target的时候为什么会报空指针异常问题

    其实也和尾插法这段代码有关

    模拟场景:

    1. 设现在的size为100

    2. 线程a拿到了链表尾部元素last 之后让出执行权给线程b

    3. 线程b也拿到了相同的last之后一直执行完了add操作,此时size = 101

    4. 线程a也执行了 l.next = newNode;覆盖了线程b的在size = 101 位置上的值,之后也进行了size++的操作,size = 102

    5. 此时出现的问题就是size = 101 位置上的值被覆盖了两次,但是102位置上的值是null。

    6. 所以遍历的时候会报空指针异常。

    7. 因为last = newNode;这一步的重复覆盖,可以预测所有的null都会是链表末尾。

      image-20211106091345360

      3. 关于为什么foreach增强for循环为什么会报空指针的问题

      Linkedlist的foreach循环,是依赖Iterator的,LinkedList也有自己的迭代器,源码如下

      image-20211106092302610

      可以看出hasnext的判断并不是node.next != null 而是 nextIndex < size 所以即使null都在末尾也会在 next = next.next的时候报空指针异常。

      3. 问题解决

      List<String> target = Collections.synchronizedList(new LinkedList<>());
      

      让需要进行add操作的list,转换成线程安全的。

      关于ArrayList

      其实也是和size++有关,与LinkedList不同的是由于实现尾插的方式不一致,所以导致null可能在中间

      源码如下:

      image-20211106093041930

      结果如下:

      image-20211106093006725

  • 相关阅读:
    如何产生 XPO 的WCF服务中间层
    法国达索公司 -- 全球出色的产品设计和体验解决方案商
    DevExpress Ribbon布局多文档界面—XtraTabControl动态增加Tab和关闭选项卡方法 (转)
    Axure 案例及基础部件
    低代码平台
    体检结果(2018年10月,胃肠镜)
    功能性肠道疾病用药一览表
    spartan 6 asynchronous reset and set
    理想
    matlab GUI 初学
  • 原文地址:https://www.cnblogs.com/iandf/p/15516014.html
Copyright © 2011-2022 走看看