我们在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的可能性。因此,当它发生时-toStringiterator.next()将抛出ConcurrentModificationException

07-24 09:29