Java - LinkedList:奇怪的NullPointerException

3
我对一个NullPointerException有些困惑,不太理解它的原因。
我的代码运行24/7,表现良好,但是这个异常会在应用程序启动后的1天到1周内随机出现。
以下是堆栈跟踪:
java.lang.NullPointerException
at java.util.LinkedList.get(LinkedList.java:477)
at com.ch4process.acquisition.ScenarioWorker.eventHandling(ScenarioWorker.java:97)
at com.ch4process.acquisition.ScenarioWorker.call(ScenarioWorker.java:79)
at com.ch4process.acquisition.ScenarioWorker.call(ScenarioWorker.java:1)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)

正如您所看到的,这个异常是在一个线程中引发的。

以下是简化后的代码:

public class ScenarioWorker implement Callable<Integer>
{
List<SignalValueEvent> eventList = new LinkedList<>();

boolean busy = false;

@Override
public Integer call() throws Exception
{
    try
    {
        while (true)
        {
            eventHandling();
            Thread.sleep(1000);
        }
    }
    catch (Exception ex)
    {
        // Redirects the exception to a custom class
    }
}

private void eventHandling()
{
    if (! busy)
    {
        while (eventList.size() > 0)
        {       
            SignalValueEvent event = eventList.get(0); // NullPointerException here according to stacktrace

            if(event.isTriggered()))
            {
                busy = true;
                doScenario(event);
            }
            deleteEvent();
        }
    }
}


private void deleteEvent()
{
    try
    {
        eventList.remove(0);
    }
    catch (Exception ex)
    {
        // Redirects the exception to custom class
    }
    finally
    {
        busy = false;
    }
}


@Override
public void SignalValueChanged(SignalValueEvent event)
{
    if (event.isValid())
    {
        eventList.add(event);
    }
}

}

[编辑:堆栈跟踪中的第97行是说SignalValueEvent event = eventList.get(0);]

这个类实现了一个接口,允许另一个类通过调用SignalValueChanged方法来通知它。

因此,基本上我的LinkedList在类的初始化时被创建,在外部类需要将事件放入列表时被填充,而调用方法循环遍历列表以查看是否有任何内容。如果有,就会处理它并删除事件。

我已经测试过了,唯一可能导致NPException的原因是我的列表等于null...但我在代码中没有这样做...

正如我所说,这段代码全天候工作,我在启动应用程序后的一周半左右才发现了这个错误。我是否漏掉了什么显而易见的东西?

非常感谢您阅读这篇文章,如果您能帮助我,我将不胜感激 :)


4
  1. 第 97 行是哪一行?ScenarioWorker.java:97
  2. 现在每次运行都会失败吗?还是出现间歇性问题?
  3. 如果是间歇性的,那么这可能是一个并发问题。你确定你的代码是线程安全的吗?
- Hovercraft Full Of Eels
1
我也认为这是一个并发问题,因为出现了不规则的失败。 - AhmadWabbi
2
《Java并发编程实战》https://www.amazon.co.uk/d/Books/Java-Concurrency-Practice-Brian-Goetz/0321349601/ref=sr_1_1?s=books&ie=UTF8&qid=1483365378&sr=1-1&keywords=java+concurrency+in+practice - 一定要拿到这本书并深入学习。如果不这样做,你将永远生活在痛苦的世界中。 - mike rodent
3
请注意,NPE并不是因为您的列表为空 - 如果是这样,您的异常根源将在ScenarioWorker.java第97行。相反,代码正在进入LinkedList的内部,这表明LinkedList实现中的某些内部问题出了问题 - 这是并发问题的一个巨大的警示标志。至于为什么会出现这种情况,可能是一个线程在同时调用SignalValueChanged和另一个线程调用eventHandling造成了竞争。 - jacobm
2
@KevinEsche:这绝对不是你发布的那个问题的重复,因为NPE与变量为空无关,而是一个并发问题。通常的NPE调试技术在这里不起作用。请看jacobm的评论以了解原因。 - Hovercraft Full Of Eels
显示剩余15条评论
4个回答

4
NPE并不是因为您的列表为空--如果是这样,那么异常的根源将在ScenarioWorker.java的第97行。相反,代码正在进入LinkedList的内部,这表明LinkedList实现中的某些内部问题正在被搞砸--这是并发问题的一个巨大红旗。至于原因,可能是一个线程调用SignalValueChanged,另一个线程调用eventHandling同时出现了竞争。
您可以通过同步对eventList的所有访问来解决它。最简单的方法可能就是将SignalValueChanged和eventHandling方法标记为synchronized。

1
是的...很好发现。正如我之前提到的,第477行(堆栈跟踪)还显示(如果您查看LinkedList、Java 8、_091的源代码),"索引检查"已经通过了...也就是说,这不是因为空列表而导致越界的问题(当然,这将产生不同的异常)。 - mike rodent

3

我认为这也与线程同步有关。解决方法:将eventList封装在一个带有synchronized方法的对象中。只通过调用这些同步方法从所有线程访问eventList


2

请阅读LinkedList的JavaDoc

注意,这个实现不是同步的。如果多个线程同时访问一个链表,并且其中至少一个线程在结构上修改了列表,则必须在外部进行同步。(结构上的修改是添加或删除一个或多个元素的任何操作;仅设置元素的值不是结构上的修改。)这通常通过对自然封装列表的某个对象进行同步来完成。如果不存在这样的对象,则应使用Collections.synchronizedList方法“包装”列表。最好在创建时完成此操作,以防止意外的未同步访问列表:

List list = Collections.synchronizedList(new LinkedList(...));


1
如Hovercraft Full Of Eels在上面所建议的那样,我猜测这是一个并发问题。很可能,在您调用eventList.get(0)的时候,尽管刚刚测试过列表不为空,但是另一个并发线程通过将该列表变为null或空来使其为空。编辑:如问题评论中所讨论的那样,NullPointerException是从LinkedList的内部工作中抛出的,这意味着它绝对是一个并发问题。使您的对象线程安全,就可以解决问题。

1
如果列表为空,我应该有一个IndexOutOfBound异常,而不是NullPointerException。而且我从来没有销毁过那个列表或将其设置为null,所以我不知道为什么会这样。 - Caerbannog
1
@Caerbannog 除了你使用的是 LinkedList 而不是 ArrayList - Code-Apprentice
@Code-Apprentice,严格来说@Caerbannog是正确的。LinkedList也会抛出IndexOutOfBounds异常。https://docs.oracle.com/javase/7/docs/api/java/util/LinkedList.html#get(int)这意味着NPE更可能是由于列表本身被设置为null,但不清楚为什么会发生这种情况。如果可能的话,我会添加一个针对空列表的测试,以查看该条件是否在运行时翻转。我仍然怀疑并发问题。 - shiri
1
不!正如jacobm在其他評論中指出的那樣,異常是由LinkedList的內部引發的,而不是eventList.get(...)!!! - mike rodent

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接