我们怎样才能获得NPE,竞争条件

时间:2017-04-04 16:50:05

标签: java multithreading

我在接受采访时被问到以下问题。给定以下代码,如果多个线程调用方法adddoAction,那么在打印NullPointerException时如何获得toString?**

public class Test{
     private List<Object> obj = new ArrayList<Object>();

     public void add(Object o){
           obj.add(o);
     }

     public void doAction(){
           for(Object o: obj){
                 System.out.println(o.toString()); // maybe NPE, why?
           }
     }

}

删除所有其他多线程问题。

4 个答案:

答案 0 :(得分:7)

首先,让我们改变变量的名称; List<Object> list=new ArrayList<>();因为&#34; obj&#34;对于引用List的变量来说,这是一个非常糟糕的名称。

好的,当程序调用{​​{1}}时,它可能需要增长数组。这意味着它必须:

  1. 分配一个新的,更大的数组,其成员全部将初始化为list.add(o);
  2. 将元素从旧数组复制到新数组。
  3. 如果线程A正在执行该操作,同时线程B正在调用null,则线程B可能会在之后从中读取新数组中的iterator.next()值线程A已经将对象引用复制到该数组的成员中。

    记住:当线程访问没有同步的内存时,读者线程有可能看到变量/字段/数组成员的更新发生在不同顺序的程序顺序中写线程实际上执行了它们。

答案 1 :(得分:4)

Test t = new Test();
t.add(null);
t.doAction(); //NPE triggered

obj列表中没有可空性保证,因此它可能包含空值。

关于多线程的问题是指ConcurrentModificationException,因为“for-all”外观在内部使用Iterator。如果在迭代时添加元素,则会引发异常。

答案 2 :(得分:4)

我不喜欢这个问题,因为它意味着知道ArrayList的实施。

如果我们假设传递的Object不为空,那么您必须使用代码进行推理。这是添加类似

的内容
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

那怎么可能发生?好吧,size字段已成功递增,但作业elementData尚未可用于阅读主题。

在这种情况下,迭代器将size存在多个元素并且可以返回一个空元素(因为它还没有完成写入)。

基本上,它是两个步骤

  1. 增量大小
  2. 写elementData [size] = e
  3. (1)可以成功,而(2)仍在飞行中。

答案 3 :(得分:2)

一种可能的方式:

  • obj中的对象是可变的。
  • .toString方法查看某些可变字段,如果该字段为null,则可能会失败,或者当字段为null且某些其他条件不成立时,则会失败。很可能,对象的方法使其变异,以便在方法完成后,对象的状态是一致的,.toString不会失败。
  • 另一个线程会改变正在打印的实例。虽然突变已经过了一半,并且对象的状态不一致,但.toString被另一个线程调用,并且调用以NPE结束。

没有堆栈跟踪很难分辨。