我们在ConcurrentModificationException的android应用中遇到了崩溃。
基本上,在我们的一个库中,它调用org.apache.http.impl.client.BasicCookieStore的addCookie方法,并引发ConcurrentModificationException。这是相关的堆栈跟踪:
ArrayList.java line 569: java.util.ArrayList$ArrayListIterator.next
Collections.java line 960: java.util.Collections$UnmodifiableCollection$1.next
....
似乎抛出ConcurrentModificationException,是因为有2个(或更多)线程试图访问BasicCookieStore类内部的数组列表。现在,假设BasicCookieStore类被标记为ThreadSafe,并且所有数组列表访问方法似乎都已同步。是什么原因造成的?提示?
这是BasicCookieStore的源代码供参考:source
最佳答案
它不一定是由线程安全性较弱引起的。当您在迭代器达到其终端状态之前为已修改的结构调用Iterator.next()时,也会发生这种情况。即使在单线程中。
例如,此代码将引发ConcurrentModificationException:
ArrayList<Object> arrayList = new ArrayList<Object>();
arrayList.add(new Object());
arrayList.add(new Object());
arrayList.add(new Object());
//...
for (Object o : arrayList) { //iterating with iterator
arrayList.remove(0); // perform some modification while
//iterating over the structure
}
如果您调查ArrayList源代码,您将看到每次修改都会增加
int modCount
字段。当您通过ArrayList.iterator()创建迭代器时,它将获取modCount的快照,并将其与当前列表的modCount
进行比较,如果每次迭代均不相等,则会失败。更新:我进行了调查,发现了
BasicCookieStore
的一些问题代码。我设法找到ConcurrentModificationException
发生的一种可能性:您在一个线程中调用BasicCookieStore.toString()
,而在另一个线程中进行了一些修改(例如addCookie()
)。此类对于快速失败的迭代器几乎是安全的:除
synchronized
之外,所有方法均为toString()
。让我们看一下代码:
@Override
public String toString() {
return cookies.toString();
}
它调用
ArrayList.toString()
:public String toString() {
Iterator<E> it = iterator();
if (! it.hasNext())
return "[]";
StringBuilder sb = new StringBuilder();
sb.append('[');
for (;;) {
E e = it.next();
sb.append(e == this ? "(this Collection)" : e);
if (! it.hasNext())
return sb.append(']').toString();
sb.append(',').append(' ');
}
}
您可以看到它使用了迭代器。因此,请考虑在执行
toString()
时,我们进行了一些修改(由于缺乏同步,这确实是可能的),例如addCookie
:public synchronized void addCookie(Cookie cookie) {
if (cookie != null) {
// first remove any old cookie that is equivalent
for (Iterator<Cookie> it = cookies.iterator(); it.hasNext();) {
if (cookieComparator.compare(cookie, it.next()) == 0) {
it.remove();
break;
}
}
if (!cookie.isExpired(new Date())) {
cookies.add(cookie);
}
}
}
此方法通常不经常对列表执行修改,但是可以。
您可以亲眼看到
modCount
的迭代器不在其终端状态时,可以增加toString
的可能性。因此,当它发生时-toString
的iterator.next()
将抛出ConcurrentModificationException
。