繁体   English   中英

线程安全的CopyOnWriteArrayList反向迭代

[英]thread-safe CopyOnWriteArrayList reverse iteration

请考虑以下代码段:

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()的调用之间的size() ,导致省略元素从迭代,或抛出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());

克隆生成列表的浅表副本。 特别是,克隆与原始数据共享内部数组。 这在说明书中并不完全明显,仅仅说明了

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

(当我读到这篇文章时,我想“当然没有复制元素;这是一个浅层副本!”这实际上意味着元素和包含它们的数组都不会被复制。)

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

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

一种简单的方法是调用#toArray方法并以相反的顺序迭代数组。

您可以随时获取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调用做好准备。

重新编辑正如@MikeFHay所指出的,迭代器上的do / while循环会在空列表上抛出NoSuchElementException 为了防止这种情况发生,我用if (li.hasNext())包装了do / while循环。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM