考虑以下代码片段:
private List<Listener<E>> listenerList = new CopyOnWriteArrayList<Listener<E>>();
public void addListener(Listener<E> listener) {
if (listener != null) {
listenerList.add(listener);
}
}
public void removeListener(Listener<E> listener) {
if (listener != null) {
listenerList.remove(listener);
}
}
protected final void fireChangedForward(Event<E> event) {
for (Listener<E> listener : listenerList) {
listener.changed(event);
}
}
protected final void fireChangedReversed(Event<E> event) {
final ListIterator<Listener<E>> li = listenerList.listIterator(listenerList.size());
while (li.hasPrevious()) {
li.previous().changed(event);
}
}
有一个可以修改和迭代的监听器列表。 我认为前向迭代(参见方法#fireChangedForward) 应该是安全的。 问题是:反向迭代(参见方法#fireChangedReversed)在多线程环境中是否也安全? 我对此表示怀疑,因为涉及两个调用:#size 和#listIterator。 如果它不是线程安全的,那么在以下情况下实现#fireChangedReversed 的最有效方法是什么:
- 优化遍历
- 尽可能避免使用锁定
- 避免使用 javax.swing.event.EventListenerList
- 首选不使用第三方库的解决方案,例如可以用自己的代码实现
最佳答案
确实,listenerList.listIterator(listenerList.size())
不是线程安全的,这正是您建议的原因:列表可能会在调用 size() 之间更改大小
和 listIterator()
,导致从迭代中遗漏一个元素,或者抛出 IndexOutOfBoundsException
。
处理此问题的最佳方法是在获取迭代器之前克隆 CopyOnWriteArrayList
:
CopyOnWriteArrayList<Listener<E>> listenerList = ... ;
@SuppressWarnings("unchecked")
List<Listener<E>> copy = (List<Listener<E>>)listenerList.clone();
ListIterator<Listener<E>> li = copy.listIterator(copy.size());
克隆对列表进行浅拷贝。特别是,克隆与原始数组共享内部数组。这在规范中并不完全明显,它只是说
Returns a shallow copy of this list. (The elements themselves are not copied.)
(当我读到这里时,我想“当然没有复制元素;这是一个浅拷贝!”这真正的意思是元素和包含它们的数组都没有被复制。)
这相当不方便,包括缺少 clone()
的协变覆盖,需要未经检查的转换。
在 JDK-6821196 中讨论了一些潜在的增强功能和 JDK-8149509 .前一个错误还链接到 discussion这个问题在并发兴趣邮件列表上。
关于java - 线程安全的 CopyOnWriteArrayList 反向迭代,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42045206/