抛出ConcurrentModificationException的ArrayList迭代器

时间:2017-05-18 16:07:12

标签: java loops arraylist concurrency iterator

我有一个带有两个访问器方法和一个通知程序的ArrayList。我的清单:

private final List<WeakReference<LockListener>> listeners = new ArrayList<>();

所有订阅操作都使用此:

public void subscribe(@NonNull LockListener listener) {
    for (Iterator<WeakReference<LockListener>> it = listeners.iterator(); it.hasNext(); ) {
        // has this one already subscribed?
        if (listener.equals(it.next().get())) {
            return;
        }
    }
    listeners.add(new WeakReference<>(listener));
}

所有取消订阅操作都使用此:

public void unsubscribe(@NonNull LockListener listener) {
    if (listeners.isEmpty()) {
        return;
    }

    for (Iterator<WeakReference<LockListener>> it = listeners.iterator(); it.hasNext(); ) {
        WeakReference<LockListener> ref = it.next();
        if (ref == null || ref.get() == null || listener.equals(ref.get())) {
            it.remove();
        }
    }
}

通知者:

private void notifyListeners() {
    if (listeners.isEmpty()) {
        return;
    }

    Iterator<WeakReference<LockListener>> it = listeners.iterator();
    while (it.hasNext()) {
        WeakReference<LockListener> ref = it.next();
        if (ref == null || ref.get() == null) {
            it.remove();
        } else {
            ref.get().onLocked();
        }
    }
}

我在测试中看到的是,notifyListeners()中的it.next()偶尔会抛出ConcurrentModificationException。我的猜测是这是由于订阅者方法中的listeners.add()。

我想我在这里误解了迭代器。我假设迭代列表保护我免受添加/删除操作引起的并发问题。

显然我错了。在更改您正在迭代的集合时,迭代器是否只是对ConcurrentModificationException的保护?例如,在迭代时调用列表中的remove()会引发错误,但调用它.remove()是安全的。

就我而言,订阅调用add()在迭代的同一列表中。我的理解在这里是否正确?

1 个答案:

答案 0 :(得分:2)

如果我正确地读了你的最后一句话,你的例子中的三个方法是从几个线程同时调用的。如果情况确实如此,那么这就是你的问题。

ArrayList 是线程安全的。无需额外同步即可同时修改它会导致未定义的行为,无论您是直接修改还是使用迭代器。

您可以同步对列表的访问(例如,使三个方法同步),或使用类似ConcurrentLinkedDeque的线程安全集合类。如果是后者,请确保阅读JavaDoc(特别是关于迭代器的每周一致的部分),以了解保证什么和不保证什么。