请考虑以下代码段:
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 的最有效方法是什么:
答案 0 :(得分:4)
确实,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());
克隆生成列表的浅表副本。特别是,克隆与原始数据共享内部数组。从规范来看,这并不是很明显,仅仅是
返回此列表的浅表副本。 (元素本身不会被复制。)
(当我读到这篇文章时,我想&#34;当然这些元素都没有被复制;这是一个浅层副本!&#34;这实际上意味着元素和包含的数组都没有它们被复制了。)
这相当不方便,包括缺少clone()
的协变覆盖,需要一个未经检查的演员。
JDK-6821196和JDK-8149509讨论了一些潜在的增强功能。前一个bug还会在并发兴趣邮件列表中链接到此问题的discussion。
答案 1 :(得分:1)
一种简单的方法是调用#toArray
方法并以相反的顺序迭代数组。
答案 2 :(得分:1)
您可以随时在列表末尾获得ListIterator
和“快进”:
final ListIterator<Listener<E>> li = listenerList.listIterator();
if (li.hasNext()) {
do{
li.next();
} while (li.hasNext());
}
while (li.hasPrevious()) {
li.previous().changed(event);
}
编辑我为前一个答案的奇怪异常处理切换了一个do / while循环,将ListIterator
的光标放在最后一个元素之后,以便为下一次previous
来电。
RE-EDIT 正如@MikeFHay所指出的,迭代器上的do / while循环会在空列表上抛出NoSuchElementException
。为了防止这种情况发生,我用if (li.hasNext())
包装了do / while循环。