【问题标题】:Java ConcurrentModificationException when iterating ArrayList迭代 ArrayList 时出现 Java ConcurrentModificationException
【发布时间】:2015-01-07 11:50:03
【问题描述】:

当迭代ArrayList 并将对象添加到辅助ArrayList 时,我得到ConcurrentModificationException。我真的不知道为什么,因为我没有编辑我正在迭代的列表。

这发生在我的代码的两个部分。这些是代码。

编辑 - 代码 1:

public static ConcurrentHashMap<Long, ArrayList<HistoricalIndex>> historicalIndexesMap = new ConcurrentHashMap<Long, ArrayList<HistoricalIndex>>();

ArrayList<HistoricalIndex> historicalIndexList = IndexService.historicalIndexesMap.get(id);
List<Double> tmpList = new ArrayList<Double>();
for(HistoricalIndex hi : historicalIndexList){ //EXCEPTION HERE
    if((System.currentTimeMillis()-hi.getTimestamp()) >= ONE_MINUTE){
        tmpList.add(hi.getIndex());
    }
}

在上面的代码 1 中,我应该像这样复制historyIndexList:

ArrayList<HistoricalIndex> historicalIndexList = new ArrayList<HistoricalIndex>(IndexService.historicalIndexesMap.get(id));

而不是这样做:?

ArrayList<HistoricalIndex> historicalIndexList = IndexService.historicalIndexesMap.get(id);

代码 2:

List<Double> tmpList = new ArrayList<Double>();
for(HistoricalIndex hi : list){ //EXCEPTION HERE
    tmpList.add(hi.getIndex());
}

有人知道为什么会这样吗?

堆栈跟踪:

21:19:50,426 ERROR [stderr] (pool-9-thread-6) java.util.ConcurrentModificationException
21:19:50,429 ERROR [stderr] (pool-9-thread-6)   at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:859)
21:19:50,432 ERROR [stderr] (pool-9-thread-6)   at java.util.ArrayList$Itr.next(ArrayList.java:831

【问题讨论】:

  • list 是什么类型,在哪里定义?
  • 你确定historyIndexList没有被任何其他线程修改过吗?
  • 典型的并发问题/竞争条件。
  • @nfechner 列表是 ArrayList 类型,它在 ConcurrentHasmap 中定义。使用此列表的每个线程在从 hashmap 中获取它后将其复制到一个临时列表中。
  • 在循环中添加日志语句并检查打印出的线程

标签: java arraylist concurrentmodification


【解决方案1】:

让我们简要了解一下导致 ConcurrentModificationException 的原因。 ArrayList 在内部维护一个修改计数值,它只是一个整数,每次对列表进行修改时都会递增。创建迭代器时,它会获取此值的“快照”。然后,每次使用迭代器时,它都会检查它的这个值的副本是否仍然与数组自己的副本匹配。如果没有,则抛出异常。

这意味着要发生 ConcurrentModificationException,必须在您第一次创建迭代器之后(即在 for() 语句第一次执行之后但在它结束之前)对 ArrayList 进行了一些修改。由于您的 for() 循环没有修改 ArrayList,这意味着在您迭代数组时必须有其他线程正在更改数组。

编辑:在回复您的编辑时,是的,如果其他线程要更改它,您应该复制该数组。你甚至可以这样做:

for(HistoricalIndex hi: new ArrayList<HistoricalIndex>(historicalIndexList))

...开始循环时复制列表。

总而言之,ConcurrentModificationException 与我们通常认为的并发问题无关。通过在迭代器循环中修改数组而不是通过迭代器的 remove() 方法,您可以很容易地在单个线程中获得一个。在这种情况下,“并发”意味着迭代和修改同时发生 - 无论是在相同的还是不同的线程中。

【讨论】:

  • 感谢您的快速答复。我会尝试复制它。
【解决方案2】:

那是因为您尝试同时访问它,而 ArrayList 不是 synchronized
你可以使用同步的java.util.Vector或者使ArrayList同步的做:

Collections.synchronizedList(new ArrayList(...)); 

作为@izca cmets,为避免并发修改,您应该将创建的列表放在同步块中:

List<T> myList = Collections.synchronizedList(new ArrayList<T>(...)); 
synchronized(myList) { 
    // to modify elements in myList
}

【讨论】:

  • Vector 或同步列表仅同步方法。它仍然允许在迭代进行时修改List,并且会产生相同的ConcurrentModificationException
猜你喜欢
  • 2017-10-18
  • 1970-01-01
  • 2013-07-15
  • 2017-02-24
  • 1970-01-01
  • 2011-07-05
  • 2015-07-06
  • 2012-11-21
  • 1970-01-01
相关资源
最近更新 更多