【问题标题】:Getting NullPointerException in multi thread environment in spite of null check尽管进行了空检查,但在多线程环境中获取 NullPointerException
【发布时间】:2015-12-02 11:58:14
【问题描述】:

我有一个名为statisticsCache 的全局缓存,它正在被多个线程同时修改和读取。即使我应用了空检查,但有时它会在负载运行中抛出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;
}

我应该应用什么预防性检查来避免这种情况?

【问题讨论】:

标签: java multithreading concurrency nullpointerexception


【解决方案1】:

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

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

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

无论导致 NPE 的确切机制是什么,您在此处所做的都不符合 JLS 要求(请参阅JLS 17.4),必须满足这些要求才能保证可预测的行为。

我应该应用什么预防性检查来避免这种情况?

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

【讨论】:

    【解决方案2】:

    我认为 statistic.getSize() 可能为 null,因此您正在尝试这样做:

    size += statistic.getSize();
    

    这会引发 NullPointerException

    你应该检查所有的统计对象是否有他们的属性“大小”!= null

    【讨论】:

    • 但是尺寸很长。怎么可以给NPE。
    • @user2492242 如果使用了Long,它可以是null,所以如果你的Statistics.getSize()有返回类型Long并且它从未设置它可能是null并且确实会导致 NPE。
    • 但不是 Long public long getCount() { return count; } public void setCount(long count) { this.count = count; } public long getSize() { 返回大小; } public void setSize(long size) { this.size = size; }
    【解决方案3】:

    问题实际上不是getSize() 方法,因为long 不能为空。真正的NPE是这样的

     List<Statistics> statistics = getStatisticsForQueue(name);
    
    for (Statistics statistic : statistics) 
    

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

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

    【讨论】:

    • 我也认为该统计信息为空,但正如您在 getStatisticsForQueue 中看到的那样,我已经应用了预防性检查: if (statistics != null && !statistics.contains(null)) //这里是避免 NPE 但有时不起作用的检查
    • 我假设 !statistics.contains(null) 检查将确保缓存中没有空条目
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-12-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-15
    • 2013-07-28
    • 1970-01-01
    相关资源
    最近更新 更多