如何调试ConcurrentModificationException?

15

我遇到了ConcurrentModificationException异常,但是通过观察我无法看出为什么会发生这种情况;抛出异常的区域和所有修改集合的地方都被包围在其中

synchronized (this.locks.get(id)) {
  ...
} // locks is a HashMap<String, Object>;

我试图捕获顽固的线程,但是通过在异常处设置断点,我只能得到一个信息,即抛出异常的线程拥有监视器,而另一个线程(程序中有两个线程)正在睡眠。


我应该如何继续?当你遇到类似的线程问题时,通常会怎么做?

6个回答

33

这可能与同步块没有关系。在遍历元素时修改集合通常会导致ConcurrentModificationException异常。

List<String> messages = ...;
for (String message : messages) {
    // Prone to ConcurrentModificationException
    messages.add("A COMPLETELY NEW MESSAGE");
}

13

与之前的帖子类似,如果您删除一个条目,也会遇到同样的问题。

例如:
for(String message : messages) {
  if (condition(message))
     messages.remove(message);
}

另一个常见的例子是清理Map。

可以通过显式使用迭代器来解决这个特定问题。

for(Iterator<String> iter = messages.iterator(); iter.hasNext();) {
   String message = iter.next();
   if (condition(message))
       iter.remove(); // doesn't cause a ConcurrentModificationException 
}

1
我仍然在iter.remove()行上遇到ConcurrentModificationException异常。 - Erik B
1
@ErikB 你解决了你的问题吗?怎么解决的? - Janus Troelsen
2
@user309483 经过性能测试,我们发现CopyOnWriteArray的性能符合我们的需求。在一些地方,我们采用了iftree提到的toRemoveSet解决方案。 - Erik B
2
我还发现,一些集合的iterator()方法只是提供了原始列表的迭代器“视图”,这就是为什么我仍然会收到错误的原因。 - Erik B
据我所知,它总是提供一个视图而不是副本。对于CopyOnWriteArrayXxx,在修改时会执行复制操作,而迭代器则看到原始数据。 - Peter Lawrey

5
有时候你的应用程序可能会变得过于复杂,某些功能可能具有太多的副作用。此外,也许另一个线程正在与该列表做一些错误的事情,而你无法轻易地找到它所在的位置。
针对我自己的问题,我编写了自己的列表系统,委托另一个列表,并且一旦锁定,所有其他修改都会抛出ConcurrentModificationException异常,因此坏的修改指令将在输出中得到异常。它还可以检测上述错误。
import java.util.*;
/** * 该类是一个可锁定的列表,用于调试列表上的ConcurrentModificationException。 * 可以使用setLocked(boolean)方法来打开/关闭锁定。当锁定时,所有对列表或迭代器的写访问都会引发ConcurrentModificationException异常。 * 简单的使用案例: * * list.setLocked(true); * * for (Object o : list.iterator()) // 现在不会引发ConcurrentModificationException异常,而会抛出其他引起此异常的指令 * { ... } * * list.setLocked(false); */ public class LockableList<E> implements List<E> { protected class LockableListIterator implements Iterator<E> { protected Iterator<E> iterator;
public LockableListIterator(Iterator<E> iterator) { this.iterator = iterator; }
public boolean hasNext() { return iterator.hasNext(); }
public E next() { return iterator.next(); }
public void remove() { checkLock(); iterator.remove(); } }
protected class LockableListListIterator implements ListIterator<E> { protected ListIterator<E> listIterator;
public LockableListListIterator(ListIterator<E> listIterator) { this.listIterator = listIterator; }
public boolean hasNext() { return listIterator.hasNext(); }
public E next() { return listIterator.next(); }
public boolean hasPrevious() { return listIterator.hasPrevious(); }
public E previous() { return listIterator.previous(); }
public int nextIndex() { return listIterator.nextIndex(); }
public int previousIndex() { return listIterator.previousIndex(); }
public void remove() { checkLock(); listIterator.remove(); }
public void set(E e) { checkLock(); listIterator.set(e); }
public void add(E e) { checkLock(); listIterator.add(e); } }
protected class LockableListSubList implements List<E> { protected List<E> list;
public LockableListSubList(List<E> list) { this.list = list; }
public int size() { return list.size(); }
public boolean isEmpty() { return list.isEmpty(); }
public boolean contains(Object o) { return list.contains(o); }
public Iterator<E> iterator() { return new LockableListIterator(list.iterator()); }
public Object[] toArray() { return list.toArray(); }
public <T> T[] toArray(T[] a) { return list.toArray(a); }
public boolean add(E e) { checkLock(); return list.add(e); }
public boolean remove(Object o) { checkLock(); return list.remove(o); }
public boolean containsAll(Collection<?> c) { return list.containsAll(c); }
public boolean addAll(Collection<? extends E> c) { checkLock(); return list.addAll(c); }
public boolean addAll(int index, Collection<? extends E> c) { checkLock(); return list.addAll(index, c); }
public boolean removeAll(Collection<?> c) { checkLock(); return list.removeAll(c); }
public boolean retainAll(Collection<?> c) { checkLock(); return list.retainAll(c); }
public void clear() { checkLock(); list.clear(); }
@Override public boolean equals(Object o) { return list.equals(o); }
@Override public int hashCode() { return list.hashCode(); }
public E get(int index) { return list.get(index); }
public E set(int index, E element) { checkLock(); return list.set(index, element); }
public void add(int index, E element) { checkLock(); list.add(index, element); }
public E remove(int index) { checkLock(); return list.remove(index); }
public int indexOf(Object o) { return list.indexOf(o); }
public int lastIndexOf(Object o) { return list.lastIndexOf(o); }
public ListIterator<E> listIterator() { return new LockableListListIterator(list.listIterator()); }
public ListIterator

使用方法如下:

List list = new LockableList(new ArrayList(...));
list.setLocked(true);
for (E e : list.iterator()) { ... } list.setLocked(false);

希望对他人有所帮助。


4
如果您需要从列表中删除几个元素,可以维护另一个类似于要删除的元素的列表,最后调用removeAll(collection)。当然,对于大量数据来说,这不是很好的选择。

2

我曾经也遇到过类似的问题,为了调试某些对象上的并发访问情况(有时使用调试器会修改运行时行为,以至于问题不再出现),我编写了一个小助手。这种方法与Francois展示的方法类似,但更加通用。也许它可以帮助一些人:https://github.com/smurf667/kongcurrent


1

在遍历动态列表时进行修改(例如,在foreach循环中),通常会收到ConcurrentModificationException异常。您可能希望确保您没有在任何地方这样做。


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