2016-05-10 10 views
0

異なるイベントのリスナーを保持するmEventMapを持ち、addListener()を使用してイベントリスナー、removeListener()およびdispatchEvent()を登録されたリスナーに登録します。 dispatchEvent(中回避方法対話型リスナーの場合はConcurrentModificationException

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

)、それはループのremoveListener(にあるとき)と呼ばれ、listenerItem = listenerIteratorでConcurrentModificationExceptionを引き起こします。前();

質問:誰かが反復している間にmEventMapデータの変更によって引き起こされるクラッシュを避ける最良の方法は何ですか?

+0

一つの方法は、すべてのCopyOnWriteArrayListと – m4mbax

+0

まず、リスナーに弱参照を保持しているのアイデアのように...あなたが削除したい要素なしリストをコピーすることであろうことは完璧です足で自分を撃つ方法。これは、聴取者の虚偽の消滅を防ぐために、他の誰かが聴取者に対して強い参照を保持することを必要とする。通常のアプリケーション設計では、そのような必要な参照を保持するものはなく、匿名リスナーインスタンスが標準です。次に、弱い参照のアイデアを放棄したら、(AWTEventMulticasterの背後にある)デザインパターンを見て、イベント配送の堅牢な方法を提供します。リストを複製する必要はありません。 – Holger

答えて

0

選択するアプローチは、リスナーオブジェクトに、「isRemoved」フラグを追加することです。したがって、removeListenerは、このリスナーが移動されたことだけをマークしますが、マークされていないディスパッチ・タイムにディスパッチするまで、リストに残ります。マークされたものはディスパッチ・ループの後にリストから削除されます。

1

のようなループに何かする前dispatchEventであなたのリストの安全なコピーを作成します。

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

しかし、これまでのリスナーのための最善のアプローチは、変更されていないとして、リスナーのリストを管理するためにCopyOnWriteArrayListを使用することですそれはあまりにも頻繁に、あなたはもうsynchronized blocksを必要としないように既にスレッドセーフです。

+0

Nicolasに感謝します! CopyOnWriteArrayListを見ていきます。作業コピーを作成すると助けになりますが、リスナーが別のスレッドから削除されることはなく、作業コピーでは引き続き削除されたものが呼び出されます。 – lannyf

+0

同期されたブロックにあることを忘れてはいけません。ただ一つのスレッドだけがこれらのクリティカルセクションを実行できるので、他の場所でそれを忘れていない限り、あなたのユースケースが可能であるとは思わないでしょう。 –

1

リストから要素を削除する場合は、暗黙のイテレータではを削除します。

あなたが明示的にイテレータを使用してのことを解決することができ:

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

ありがとうm4mbax、それは同じでした:ListIterator >> listenerIterator = listener.listIterator(listers.size()); イテレータがまだトラバースしている間、ノードが削除されました。 – lannyf

0

もう1つのアプローチは、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); 
     } 

    } 
関連する問題