线程安全的 CopyOnWriteArrayList 反向迭代

Hol*_*ger 5 java collections

考虑以下代码片段:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

有一个可以修改和迭代的监听器列表。我认为前向迭代(参见方法#fireChangedForward)应该是安全的。 问题是:反向迭代(参见方法#fireChangedReversed)在多线程环境中也安全吗? 我对此表示怀疑,因为涉及两个调用:#size 和#listIterator。 如果它不是线程安全的,那么在以下情况下实现#fireChangedReversed的最有效方法是什么:

  • 优化遍历
  • 如果可能的话避免使用锁定
  • 避免使用 javax.swing.event.EventListenerList
  • 更喜欢不使用第三方库的解决方案,例如可以在自己的代码中实现

Stu*_*rks 5

事实上,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());
Run Code Online (Sandbox Code Playgroud)

克隆会生成列表的浅表副本。特别是,克隆与原始版本共享内部数组。从规范中来看,这一点并不完全明显,它只是说

返回此列表的浅表副本。(元素本身不会被复制。)

(当我读到这里时,我想“当然元素没有被复制;这是一个浅拷贝!”这真正的意思是元素和包含它们的数组都没有被复制。)

这是相当不方便的,包括缺乏 的协变覆盖clone(),需要未经检查的强制转换。

JDK-6821196JDK-8149509中讨论了一些潜在的增强功能。前一个错误还链接到并发兴趣邮件列表上对此问题的讨论。