2016-05-10 11 views
0

Mit einer mEventMap, um die Listener für verschiedene Ereignisse zu halten, und hat addListener(), um die Ereignislistener, removeListener() und dispatchEvent() zu den registrierten Listenern zu registrieren.So vermeiden ConcurrentModificationException für den Listener-Fall

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

    … 
} 

Anwendungsfall

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

addEventListener(mEventListener); 

in dispatch(), während es in der Schleife ist das removeListener() aufgerufen wird und bewirkt, dass die ConcurrentModificationException bei listenerItem = listenerIterator. Bisherige();

Frage: Was ist der beste Weg, um den Absturz zu vermeiden, der durch die Änderung der mEventMap-Daten verursacht wird, während jemand darauf iteriert.

+0

Eine Möglichkeit, die Liste ohne das Element, das Sie entfernen möchten, kopieren wäre ... Wie CopyOnWriteArrayList – m4mbax

+0

Vor allem der Idee des Haltens schwachen Verweises auf Zuhörer ist die perfekte Möglichkeit, sich in den Fuß zu schießen. Das erfordert, dass jemand anders einen starken Bezug zum Zuhörer hält, um das unechte Verschwinden von Zuhörern zu verhindern. Im üblichen Anwendungsdesign gibt es niemanden, der solche erforderlichen Referenzen hält und anonyme Listener-Instanzen sind die Norm. Wenn Sie dann die Idee der schwachen Referenzen aufgegeben haben, sehen Sie sich das Entwurfsmuster hinter "AWTEventMulticaster" an, das eine robuste Art der Ereignisauslieferung bietet und immun gegen Hinzufügungen und Entfernungen zwischen den beiden ist (ohne dass eine Liste geklont werden muss). – Holger

Antwort

0

Der Ansatz ist zu wählen, ein Flag im Listener-Objekt hinzuzufügen, ‚isRemoved‘. Der removeListener würde also nur diesen Listener markieren, aber er wird in der Liste bleiben bis zum Versandzeitpunkt, der nur an diejenigen versendet, die nicht markiert sind. Und alle markierten werden nach der Dispatch-Schleife aus der Liste entfernt.

1

Erstellen Sie eine sichere Kopie Ihrer Liste in dispatchEvent vor der Schleife so etwas wie:

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

Aber bei weitem der beste Ansatz für die Hörer ist CopyOnWriteArrayList zu verwenden, um die Liste der Zuhörer zu verwalten, wie Sie nicht ändern, es ist zu oft und es ist bereits Thread sicher, so dass Sie keine synchronized blocks mehr benötigen.

+0

danke Nicolas! Werfen Sie einen Blick auf CopyOnWriteArrayList. Kopieren zur Arbeit würde helfen, aber nicht verhindern, dass der Listener aus verschiedenen Threads entfernt wird und in der Arbeitskopie wird immer noch in die entfernten aufgerufen. – lannyf

+0

Vergessen Sie nicht, dass Sie sich in einem synchronisierten Block befinden, sodass nur ein Thread diese kritischen Abschnitte ausführen kann. Daher glaube ich nicht, dass Ihr Anwendungsfall möglich ist, es sei denn, Sie haben vergessen, es an anderer Stelle zu schützen –

1

Ihr Problem ist, dass Sie ein Element aus der Liste entfernen möchten, während es mit einem impliziten Iterator durchlaufen.

Sie konnten das lösen, indem sie explizit einen Iterator:

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

Danke m4mbax, es hat das gleiche getan: ListIterator >> listenerIterator = listeners.listIterator (listeners.size()); Das Problem ist Knoten wurde entfernt, während der Iterator noch traversiert. – lannyf

0

Ein anderer Ansatz wäre die Verwendung von Javas eingebauter EventListenerList-Klasse https://docs.oracle.com/javase/8/docs/api/index.html?javax/swing/event/EventListenerList.html. Obwohl es in den javax.swing-Paketen enthalten ist, habe ich festgestellt, dass es für die Ereignisbenachrichtigung im Allgemeinen sehr nützlich ist. Es wird von einem Array unterstützt, verwaltet Listener nach Klasse und löst keine gleichzeitigen Mod-Exceptions aus. Firing ein Ereignis wie das getan:

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

    }