如何避免ConcurrentModificationException听一次监听器的情况

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何避免ConcurrentModificationException听一次监听器的情况相关的知识,希望对你有一定的参考价值。

使用mEventMap来保存不同事件的侦听器,并使用addListener()将eventlistener,removeListener()和dispatchEvent()注册到已注册的侦听器。

public void addListener(EventListener listener) {
    synchronized (mEventMap) {

        List<WeakReference<EventListener<Event>>> listeners = mEventMap.get(listener.mEventClass);
WeakReference<EventListener<Event>> listenerRef = new WeakReference<>(
                (EventListener<Event>) listener)
        …
        listeners.add(listenerRef);

        …
    }   

}

public void removeListener(EventListener listener) {
    synchronized (mEventMap) {
        List<WeakReference<EventListener<Event>>> listeners = mEventMap.get(listener.mEventClass);

        …
        if (contains(listeners, listener)) {
                doRemove(listeners, listener);
            }
       …

    }
}


public boolean dispatchEvent(Event event) {
synchronized (mEventMap) {
    List<WeakReference<EventListener<Event>>> listeners = mEventMap.get(event.getClass());
    ListIterator<WeakReference<EventListener<Event>>> listenerIterator = listeners.listIterator(listeners.size());
    …
    while (listenerIterator.hasPrevious()) {
            WeakReference<EventListener<Event>> listenerItem = listenerIterator.previous();
            EventListener<Event> listenerRef = listenerItem.get();
            if (listenerRef != null) {
                listenerRef.onEvent(event);
            } else {
                listenerIterator.remove();
            }
        }

    …
}

用例

EventListener<Event> mEventListener = new EventListener<Event>(
        Event.class) {
    @Override
    public boolean onEvent(Event event) {
        eMgr.removeListener(mEventListener);
        // do something
    }
};

addEventListener(mEventListener);

在dispatchEvent()中,当它在循环中时,调用removeListener()并在listenerItem = listenerIterator.previous()中引起ConcurrentModificationException。

问题:当有人在迭代时,什么是避免因更改mEventMap数据而导致崩溃的最佳方法。

答案

在循环之前在dispatchEvent中创建列表的安全副本:

List<WeakReference<EventListener<Event>>> listeners 
                          = new ArrayList<>(mEventMap.get(event.getClass()));

但到目前为止,听众最好的方法是使用CopyOnWriteArrayList来管理监听器列表,因为你不经常修改它,它已经是线程安全的,这样你就不再需要任何synchronized blocks了。

另一答案

您的问题是您希望在使用隐式迭代器遍历它时从列表中删除元素。

您可以通过显式使用迭代器来解决这个问题:

for (Iterator<EventListener> it = list.iterator(); it.hasNext(); ) {
    EventListener el = it.next();
    it.remove();
}
另一答案

选择的方法是在侦听器对象中添加一个标志'isRemoved'。因此removeListener只会标记此侦听器已被移动,但它将保留在列表中,直到调度时间仅调度到未标记的那些。并且在调度循环之后,所有标记的都将从列表中删除。

另一答案

另一种方法是使用java内置的EventListenerList类https://docs.oracle.com/javase/8/docs/api/index.html?javax/swing/event/EventListenerList.html。虽然它在javax.swing包中,但我发现它对于事件通知非常有用。它由数组支持,按类管理侦听器,并且不会抛出并发mod异常。发射事件就像这样:

Object[] listeners = listenerList.getListenerList();
    for(int i = listeners.length - 2; i >= 0; i -= 2){
        if(listeners[i] == YourListenerClass.class){
            ((YourListenerClass)listeners[i + 1]).yourListenerMethod(yourEvent);
        }

    }

以上是关于如何避免ConcurrentModificationException听一次监听器的情况的主要内容,如果未能解决你的问题,请参考以下文章

如何避免重复的工作?

DDoS攻击是啥?应该如何避免?

Mongodb 连接失败,怎么避免抛错影响正常流程

Java中,当的数据过大时,如何避免值的改变?

如何避免嵌套订阅超过两个级别

如何触发(不避免!) HttpClient 死锁