线程安全的CopyOnWriteArrayList反向迭代 [英] thread-safe CopyOnWriteArrayList reverse iteration

查看:98
本文介绍了线程安全的CopyOnWriteArrayList反向迭代的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

请考虑以下代码段:

 私有列表< 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) );
}
}

受保护的最终空白fireChangedForward(Event< E> event){
for(Listener< E> listener:listenerList){
监听器.changed(event);
}
}

受保护的最终void fireChangedReversed(Event< E> event){
final ListIterator< Listener< E>> li = listenerList.listIterator(listenerList.size());
while(li.hasPrevious()){
li.previous()。changed(event);
}
}

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




  • 优化遍历

  • 尽可能避免使用锁定

  • 避免使用javax.swing.event.EventListenerList

  • 首选不使用第三方库的解决方案,例如可能以自己的代码实现


解决方案

实际上, listenerList.listIterator( listenerList.size())并不是线程安全的,正是出于您建议的原因:该列表可能会在调用 size()和 listIterator(),导致从迭代中忽略元素,或者抛出 IndexOutOfBoundsException



处理此问题的最佳方法是在获取迭代器之前克隆 CopyOnWriteArrayList

  CopyOnWriteArrayList< Listener< E>> listenerList = ...; 
@SuppressWarnings( unchecked)
List< Listener< E>>复制=(List< Listener< E>>)listenerList.clone();
ListIterator< Listener< E>> li = copy.listIterator(copy.size());

克隆会复制列表的浅表副本。特别是,克隆与原始磁盘共享内部阵列。从规范中并不能完全看出这一点,该规范只说


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


(当我读到这篇文章时,我以为当然,元素不会被复制;

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



JDK-6821196 JDK-8149509 。前一个错误还链接到对此的讨论并发邮件列表上的问题。


Consider the following code snippet:

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);
    }
}

There is a listener list that can be modified and iterated. I think the forward iteration (see method #fireChangedForward) should be safe. The question is: is the reverse iteration (see method #fireChangedReversed) also safe in a multi-threaded environment? I doubt that, because there are two calls involved: #size and #listIterator. If it's not thread-safe, what is the most efficient way to implement #fireChangedReversed under the following circumstances:

  • optimize for traversal
  • avoid usage of locking if possible
  • avoid usage of javax.swing.event.EventListenerList
  • prefer solution without usage of third-party lib, e.g. implementation in own code possible

解决方案

Indeed, listenerList.listIterator(listenerList.size()) is not thread-safe, for exactly the reason you suggested: the list could change size between the calls to size() and listIterator(), resulting in either the omission of an element from the iteration, or IndexOutOfBoundsException being thrown.

The best way to deal with this is to clone the CopyOnWriteArrayList before getting the iterator:

    CopyOnWriteArrayList<Listener<E>> listenerList = ... ;
    @SuppressWarnings("unchecked")
    List<Listener<E>> copy = (List<Listener<E>>)listenerList.clone();
    ListIterator<Listener<E>> li = copy.listIterator(copy.size());

The clone makes a shallow copy of the list. In particular, the clone shares the internal array with the original. This isn't entirely obvious from the specification, which says merely

Returns a shallow copy of this list. (The elements themselves are not copied.)

(When I read this, I thought "Of course the elements aren't copied; this is a shallow copy!" What this really means is that neither the elements nor the array that contains them are copied.)

This is fairly inconvenient, including the lack of a covariant override of clone(), requiring an unchecked cast.

Some potential enhancements are discussed in JDK-6821196 and JDK-8149509. The former bug also links to a discussion of this issue on the concurrency-interest mailing list.

这篇关于线程安全的CopyOnWriteArrayList反向迭代的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆