0

我们的 android 应用程序在 ConcurrentModificationException 上经常崩溃。

基本上,在我们的一个库中,它调用 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

4

2 回答 2

1

它不一定是由弱线程安全引起的。当您为在迭代器到达其终端状态之前修改的结构调用 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);
        }
    }
}

此方法通常不经常对列表执行修改,但确实如此。您可以自己看到,modCounttoString' 的迭代器未处于其终端状态时,有可能增加。所以当它发生时 -'stoStringiterator.next()抛出一个ConcurrentModificationException.

于 2013-09-30T19:24:27.320 回答
0

来自 ConcurrentModificationException 的javadoc:

请注意,此异常并不总是表示对象已被不同的线程同时修改。如果单个线程发出一系列违反对象约定的方法调用,则该对象可能会抛出此异常。例如,如果线程在使用快速失败迭代器迭代集合时直接修改了集合,则迭代器将抛出此异常。

这是链接: http ://docs.oracle.com/javase/1.5.0/docs/api/java/util/ConcurrentModificationException.html

于 2013-09-30T19:26:47.643 回答