【问题标题】:Getting ConcurrentModificationException when running code on hosts在主机上运行代码时出现 ConcurrentModificationException
【发布时间】:2021-04-27 09:41:12
【问题描述】:

在生产环境中运行以下代码时出现 ConcurrentModificationException。该问题似乎是间歇性的(当我尝试调用使用以下代码的 api 时,它工作正常)。

我检查了类似的问题,但在每个问题中,有人会遍历一个列表并同时对其进行修改。我不确定以下代码有什么问题以及为什么会引发异常。任何指针都非常感谢。

谢谢。

private String getLookupKeyForODPair () {
    
    final List<LookUpKey> lookUpKeyList = transitTimeConfiguration.getLookUpKeys();
    
    // Build lookUpKey
    final String lookUpKey = buildLookupKey(lookUpKeyList, originDestinationPair, merchantId);

    // perform operations on lookupKey and not on lookupKeyList
    
}

private String buildLookupKey(final List<LookUpKey> lookUpKeyList,
                              final ODPair odPair,
                              final String id) {
    lookUpKeyList.sort(Comparator.comparing(LookUpKey::getOrder));
    
    final List<String> keyList = new ArrayList<>();
    
    lookUpKeyList.forEach(key -> {
        switch (key.getName()) {
            case Constants.ID:
                keyList.add(id);
                break;
            case Constants.ZIP_CODE:
                keyList.add(odPair.zipCode);
                break;
            case Constants.COUNTRY_CODE:
                keyList.add(odPair.countryCode);
                break;
            default:
    
        }
    });
    
    return String.join(MiscellaneousConstants.COMMA, keyList);
}

堆栈跟踪

Caused by: java.util.ConcurrentModificationException
    at java.util.ArrayList.forEach(ArrayList.java:1262) ~[?:1.8.0_282]
    at com.placing.lookupKeyService.component.impl.LookupKeyComponentImpl.buildLookupKey(LookupKeyComponentImpl.java:410) ~[LookupKeyService-1.0.jar:?]
    at com.placing.lookupKeyService.component.impl.LookupKeyComponentImpl.getLookupKeyForODPair(LookupKeyComponentImpl.java:221) ~[LookupKeyService-1.0.jar:?]
    at com.placing.lookupKeyService.component.impl.LookupKeyComponentImpl.lambda$null$4(LookupKeyComponentImpl.java:196) ~[LookupKeyService-1.0.jar:?]
    at java.lang.Iterable.forEach(Iterable.java:75) ~[?:1.8.0_282]
    at com.placing.lookupKeyService.component.impl.LookupKeyComponentImpl.lambda$getLookupKeyForAllConfigurations$5(LookupKeyComponentImpl.java:196) ~[LookupKeyService-1.0.jar:?]
    at java.util.ArrayList.forEach(ArrayList.java:1259) ~[?:1.8.0_282]
    at com.placing.lookupKeyService.component.impl.LookupKeyComponentImpl.getLookupKeyForAllConfigurations(LookupKeyComponentImpl.java:185) ~[LookupKeyService-1.0.jar:?]
    at com.placing.lookupKeyService.component.impl.LookupKeyComponentImpl.lambda$getAllLookupKeys$2(LookupKeyComponentImpl.java:160) ~[LookupKeyService-1.0.jar:?]

【问题讨论】:

  • 堆栈跟踪可以帮助识别问题……除此之外,临时的ArrayList 是不必要的。您可以使用 Stream 操作来映射值并将它们首先连接到字符串。此外,它允许在不修改传入的lookUpKeyList 的情况下对流元素进行排序。

标签: java exception arraylist concurrency


【解决方案1】:

我实际上不确定是否是引发异常的代码,异常的堆栈跟踪打印将有助于确定原因。

但是,您提到了在使用 ArrayList 时可能会遇到的一个经典问题,尤其是在多线程环境中。
事实上,很容易遇到ConcurrentModificationException,首先是因为当您遍历java.util.ArrayList(隐式或显式)时创建的迭代器采用“快速失败”行为。这意味着每次您在遍历列表时尝试修改列表并且不使用迭代器本身中定义的方法 removeadd 时,都会立即抛出 ConcurrentModificationException

对于您的具体情况,您没有提供任何超出该方法的信息,但我可以想象 lookUpKeyList 列表正在由另一个线程在其他地方修改,而您正在通过 buildLookupKey 方法遍历它使用forEach 时。
这也可以解释为什么错误会间歇性发生,因为在使用ArrayList 处理多线程环境时,您可能会遇到不可预知的情况。
这是该特定列表的默认行为,您对此无能为力。

但是,还有其他类型的列表可以帮助您,这取决于您的情况。
例如,您可以使用java.util.concurrent.CopyOnWriteArrayList

我建议你用CopyOnWriteArrayList 包裹你的列表,这样可以保证ConcurrentModificationException 永远不会被抛出,因为每次调用addremove 在列表上被调用,一个新的新鲜列表的副本创建为下划线。

举个例子,你应该有这样的东西:

import java.util.concurrent.CopyOnWriteArrayList;
...
private String buildLookupKey(final List<LookUpKey> lookUpKeyList,
                          final ODPair odPair,
                          final String id) {
    CopyOnWriteArrayList<LookUpKey> copyOflookUpKeyList = new CopyOnWriteArrayList<>(lookUpKeyList);
     
    copyOflookUpKeyList.sort(Comparator.comparing(LookUpKey::getOrder));

    final List<String> keyList = new ArrayList<>();

    copyOflookUpKeyList.forEach(key -> {
        switch (key.getName()) {
            case Constants.ID:
                keyList.add(id);
                break;
            case Constants.ZIP_CODE:
                keyList.add(odPair.zipCode);
                break;
            case Constants.COUNTRY_CODE:
                keyList.add(odPair.countryCode);
                break;
            default:

        }
    });

    return String.join(MiscellaneousConstants.COMMA, keyList);
}

希望能解决您的问题,但请让我了解更多信息,以便我可以进一步帮助您。

【讨论】:

  • 非常感谢 Marco 的快速回复。我已经添加了方法的调用方式以及运行时发出的主机日志。该错误确实仅指向 buildLookupKey 方法。
  • 是的,我查看了堆栈跟踪,并没有说太多,但是错误显然是由于在迭代时修改了lookUpKeyList列表引起的。正如我所说,该列表可能会被另一个线程修改,这就是异常的原因。正如我上面建议的那样,尝试用 CopyOnWriteArrayList 之一包装您的 ArrayList,这应该可以解决您的问题。如果是这样并且您对答案感到满意,请接受并投票,以便将来可以帮助其他开发人员解决与您相同或相似的问题。谢谢。
  • 当然。我会试试这个并确认它是否有效。谢谢。
  • API 的处理也只是同步的,所以这里应该有一个线程来完成所有的工作,对吧?我仍然会尝试使用 CopyOnWriteArrayList,因为我没有看到任何其他原因
  • 当您创建lookUpKeyList 的本地副本时,它不必是CopyOnWriteArrayList,因为在当前方法执行之外没有对该副本的引用,所以一个普通的@987654339 @ 也可以。关键方面是本地副本,换句话说,不修改提供的调用者 lookUpKeyList 并仅迭代本地副本。
猜你喜欢
  • 2011-07-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-10-16
相关资源
最近更新 更多