尽管进行了空检查,但在多线程环境中获取NullPointerException

时间:2015-12-02 11:58:14

标签: java multithreading concurrency nullpointerexception

我有一个名为statisticsCache的全局缓存,它被多个线程同时修改和读取。即使我已经应用了null检查,但有时它会在加载运行中抛出NullPointerException。有关详细信息,请参阅下文:

static Map<String, List<Statistics>> statisticsCache = new ConcurrentHashMap<String, List<Statistics>>();

// method to read the global  cache
List<Statistics> getStatisticsForQueue(String name) {
    List<Statistics> statsCopy = Collections.emptyList();
    List<Statistics> statistics = statisticsCache.get(name);
    if (statistics != null && !statistics.contains(null)) //Here is the check to avoid NPE but sometimes does not works
        statsCopy = new ArrayList<Statistics>(statistics);
    return statsCopy;
}

//method to write into global cache
private void setStatisticsListForQueue(String name) {
    // flushing all pending Last writes of buckets of a queue to DB
    flushStatisticToDB(name);
    if (!statisticsCache.containsKey(name)) {
        statisticsCache.put(name, new ArrayList<Statistics>(1));
    }
    List<Statistics> queueStatisticsList = queueServiceMetaDao
            .findStatisticsByname(name);
    if (queueStatisticsList != null && !queueStatisticsList.isEmpty()) { 
        for (Statistics statistic : queueStatisticsList) {
            // to avoid NPE
            if (statisticsCache.get(name).contains(statistic)) {
                statisticsCache.get(name).remove(statistic);
            }
            statisticsCache.get(name).add(statistic);
        }
    } else {
        statisticsCache.put(name, new ArrayList<Statistics>(1));
    }
}

//method where I am getting NPE 
public long getSize(String name) {
    long size = 0L;
    List<Statistics> statistics = getStatisticsForQueue(name);
    for (Statistics statistic : statistics) {
        size += statistic.getSize(); //Sometimes it throws NullPointerException
    }
    return size;
}

我应该采取哪些预防性检查来避免这种情况?

3 个答案:

答案 0 :(得分:2)

  

即使我已经应用了空检查,但有时它会在加载运行中抛出NullPointerException

好的,所以如果你有多个线程执行这个代码,那么(IMO)最可能的解释是代码没有正确同步。当然,地图本身是ConcurrentHashMap,因此应该是线程安全的。但是,您有多个线程创建,访问和修改ArrayLists,而不会在列表上进行任何互斥或其他同步。

有很多事情可能会出错。一种可能性是一个线程从列表中删除一个元素,第二个线程同时在同一个列表上调用getSize()。一种可能的结果是getSize()中的迭代将看到列表大小的陈旧值,并返回一个已被其他线程删除取消的数组元素。由于列表中两个线程的操作没有同步,因此关于一个线程列表的可见性变为另一个线程的“所有下注都关闭”。

无论确切机制导致NPE,您在此处所做的工作都不符合JLS要求(请参阅JLS 17.4),以确保可预测的行为。

  

我应该采取哪些预防性检查来避免这种情况?

你无法解决这个问题。您需要在列表上进行适当的同步,以确保读取和更新不会重叠。您还需要使用putIfAbsent而不是if (! containsKey) { put ... }来处理其他竞争条件。

答案 1 :(得分:0)

我认为statistic.getSize()可能为null,因此您尝试执行此操作:

size += statistic.getSize();

抛出NullPointerException

您应该检查所有统计信息对象是否具有其属性&#34; size&#34; != null

答案 2 :(得分:0)

问题实际上不是getSize()方法,因为long不能为null。实际上NPE就是这个

 List<Statistics> statistics = getStatisticsForQueue(name);

for (Statistics statistic : statistics) 

如果统计数据为null,则for循环将具有NPE。那么你可以做些什么来避免这种情况

if(statistics != null)
  for (Statistics statistic : statistics)