【问题标题】:how to avoid ConcurrentModificationException for listen once listener case如何避免 ConcurrentModificationException 侦听一次侦听器案例
【发布时间】:2016-09-05 10:09:57
【问题描述】:

有一个 mEventMap 来保存不同事件的监听器,并有 addListener() 来注册事件监听器,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 数据更改而导致崩溃的最佳方法是什么。

【问题讨论】:

  • 一种方法是复制没有要删除的元素的列表...例如 CopyOnWriteArrayList
  • 首先,对听众持有弱引用的想法是自取其辱的完美方式。这需要其他人对听众有强烈的参考,以防止听众的虚假消失。在通常的应用程序设计中,没有人持有此类必需的引用,并且匿名侦听器实例是常态。然后,一旦你放弃了弱引用的想法,看看AWTEventMulticaster 背后的设计模式,它提供了一种强大的事件传递方式,不受中间添加和删除的影响(无需克隆列表)。

标签: java multithreading event-handling


【解决方案1】:

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

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

但到目前为止,对于侦听器来说最好的方法是使用CopyOnWriteArrayList 来管理侦听器列表,因为您不会经常修改它并且它已经是线程安全的,因此您不再需要任何synchronized blocks .

【讨论】:

  • 感谢尼古拉斯!将看看 CopyOnWriteArrayList。制作副本会有所帮助,但不会阻止从不同线程中删除侦听器,并且在工作副本中仍会调用已删除的侦听器。
  • 不要忘记您处于同步块中,因此只有一个线程可以执行这些关键部分,所以我不相信您的用例是可能的,除非您忘记在其他地方保护它
【解决方案2】:

你的问题是你想从列表中删除一个元素同时使用隐式迭代器遍历它。

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

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

【讨论】:

  • 感谢 m4mbax,它做了同样的事情: ListIterator>> listenerIterator = listeners.listIterator(listeners.size());问题是节点在迭代器仍在遍历时被删除。
【解决方案3】:

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

【讨论】:

    【解决方案4】:

    另一种方法是使用 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);
            }
    
        }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-07-25
      • 1970-01-01
      • 2011-08-16
      • 1970-01-01
      • 2013-12-18
      • 2021-07-12
      相关资源
      最近更新 更多