我有一个带有两个访问器方法和一个通知程序的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()在迭代的同一列表中。我的理解在这里是否正确?
答案 0 :(得分:2)
如果我正确地读了你的最后一句话,你的例子中的三个方法是从几个线程同时调用的。如果情况确实如此,那么这就是你的问题。
ArrayList 不是线程安全的。无需额外同步即可同时修改它会导致未定义的行为,无论您是直接修改还是使用迭代器。
您可以同步对列表的访问(例如,使三个方法同步),或使用类似ConcurrentLinkedDeque的线程安全集合类。如果是后者,请确保阅读JavaDoc(特别是关于迭代器的每周一致的部分),以了解保证什么和不保证什么。