并发场景下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

posted @ 2021-11-06 09:40  ${yogurt}  阅读(439)  评论(0编辑  收藏  举报