单线程代码出现ConcurrentModificationException异常

5

编辑:我感到非常尴尬。我看错了构造函数。实际被调用的构造函数(如下所示)违反了foreach循环的并发规则,正如Kal的回答所述。

无论如何,非常感谢您的帮助!这仍然会帮助我修复代码中的实际错误。

全部

我是一名相当新的Java程序员,刚开始对该语言有了基本的掌握。我目前正在使用一个对话参与者系统,但首先得尝试将我们系统的逻辑术语表示达到规范要求。我已经快完成了,但遇到了以下错误:

Exception in thread "main" java.util.ConcurrentModificationException
at java.util.AbstractList$Itr.checkForComodification(AbstractList.java:372)
at java.util.AbstractList$Itr.next(AbstractList.java:343)
at com.Term.<init>(Term.java:97)
at com.Term.substituteVariables(Term.java:251)
at com.Term.substituteVariables(Term.java:247)
at com.Term.substituteVariables(Term.java:247)
at com.TermPredTestArch.main(TermPredTestArch.java:40)

所涉及的方法 substituteVariables 基本上是一个拷贝构造函数,只不过稍作修改:它接收绑定映射的一个 map,并递归迭代调用它的 Term 对象,沿途查找变量并将其替换为其实例化。令人困惑的是,昨晚我离开时它似乎还能正常工作(尽管我没有进行全面测试),但现在却不听使唤了;我没有做出任何重大修改。

相关代码如下(232-252行):

232  /** Returns a new Term with the appropriate bindings substituted */
233  public Term substituteVariables(Map<Variable, Symbol> bindings) {
234    ArrayList<Symbol> args    = this.getArgs();
235    ArrayList<Symbol> newArgs = new ArrayList<Symbol>();
236    Set<Variable> vars        = this.getVars();
237    Set<Variable> bindingKeys = bindings.keySet();
238    for(Symbol s: args) {
239      // if s is a Variable, check to see if it has a substituion, and
240      // if so, swap it out
241      if(s instanceof Variable) {
242        if(bindingKeys.contains(s)) newArgs.add(bindings.get(s));
243        else                        newArgs.add(s);
244      // if s is a Term, add it and recursively substitute any variables
245      // it has within the current set of bindings
246      } else if(s instanceof Term) {
247        newArgs.add(((Term) s).substituteVariables(bindings));
248      // if s is just a symbol, simply add it to the args
249      } else newArgs.add(s);
250    }
251    return new Term(this.getName(), newArgs);
252  }

编辑:这是Term的构造函数:

public Term(String n, ArrayList<Symbol> a) {
    super(n);
    args = a;
    HashSet<Variable> varsToAdd = new HashSet<Variable>();
    for(Symbol s: a) parseArg(s.toString());
}          

那实际上被调用的构造函数并不是我原以为的那一个。这确实违反了foreach循环的并发规则,正如Kal的回答所述。从我已经做过的研究来看,我知道ConcurrentModificationException通常是由多个线程在没有同步的情况下同时迭代/修改集合引起的,但我这里没有故意的并行性,也没有在类或使用它的测试代码中任何其他地方。否则,我并不完全确定。类的javadoc提到它也可能是由于迭代器同时迭代和修改集合引起的,但我认为我也没有这样做;我只是观察迭代的集合并使用其中的信息来构建另一个集合。这是否也违反了并发规定呢?非常感谢大家能够提供任何指针!我也预先为任何Java礼仪或风格的严重违规道歉(请随时指出!)。谢谢。

我认为你需要展示Term构造函数,因为它是导致错误的原因。 - Anon
Term 的构造函数是什么样子的?这就是异常抛出的地方。 - Jim Garrison
@Derek:如果你看到某个答案回答了你的问题,请在其侧面按下接受勾号。回答者会得到他的声誉 :) - mico
2个回答

14

使用for循环遍历ArrayList时,如果在遍历过程中修改了ArrayList,将会抛出ConcurrentModificationException异常。

正确的做法是使用迭代器(Iterator)的add/remove方法进行操作。

以下是相关API文档的描述 --

此类的iterator和listIterator方法返回的迭代器属于fail-fast类型: 如果在迭代器创建之后、任何时间内以任何方式(除非通过迭代器自身的remove或add方法)对列表进行结构性修改,则迭代器将抛出ConcurrentModificationException异常。因此,在面对并发修改时,迭代器会快速干净地失败,而不会在未来的某个不确定时间冒险产生任意的非确定性行为。


Iterator没有 add(...) 方法,尽管ListIterator有。 - mre

0
newArgs.add(((Term) s).substituteVariables(bindings));

所以在这段代码中,你遇到了错误;就像Kal所说的那样,当你编辑正在迭代的ArrayList时,它会发生。要解决这个问题,请更改

for(Symbol s: args) {

for (int i = 0; i < args.size(); i++) {
    Symbol s = args.get(i);

1
多线程使用警告:如果您不通过适当的同步来修复根本原因,最终会遇到其他问题。例如,如果在args.size()args.get()之间调用了ArrayList.clear(),则args.get(i)将返回null。您提供的代码仅适用于单线程。 - Matthieu

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