スレッドセーフなリスナーを実装するとき、私は通常、どのタイプのCollection
リスナーを保持するのが最適かを考えます。これまでに 3 つのオプションを見つけました。
標準では、単純な へのアクセスをObservable
使用します。リスナーのコピーを使用することはオプションですが、次のような問題を防ぐため、良いアイデアを伝えることができる限りsynchronized
ArrayList
- リスナーはコールバックで自分自身を削除します ( - それを防ぐために逆の順序で
ConcurrentModificationException
インデックス付きループを反復することができます)for
- 同期ブロック内で外部コードを実行すると、全体がブロックされる可能性があります。
実装は残念ながら数行以上です。Collections.synchronizedList()
は必要ありませんがsynchronized
、removeListener
それほど価値はありません。
class ObservableList {
private final List<Listener> listeners = new ArrayList<Listener>();
public void addListener(Listener listener) {
synchronized (listeners) {
if (!listeners.contains(listener)) {
listeners.add(listener);
}
}
}
public void removeListener(Listener listener) {
synchronized (listeners) {
listeners.remove(listener);
}
}
protected void notifyChange() {
Listener[] copyOfListeners;
synchronized (listeners) {
copyOfListeners = listeners.toArray(new Listener[listeners.size()]);
}
// notify w/o synchronization
for (Listener listener : copyOfListeners) {
listener.onChange();
}
}
}
しかし、本質的にスレッドセーフであり、より効率的である可能性があるCollection
inがあります。これは、それらの内部ロック メカニズムが単純なブロックjava.util.concurrent
よりも最適化されていると想定しているためです。synchronized
通知ごとにコピーを作成することも非常にコストがかかります。
に基づくCopyOnWriteArrayList
class ObservableCopyOnWrite {
private final CopyOnWriteArrayList<Listener> listeners = new CopyOnWriteArrayList<>();
public void addListener(Listener listener) {
listeners.addIfAbsent(listener);
}
public void removeListener(Listener listener) {
listeners.remove(listener);
}
protected void notifyChange() {
for (Listener listener : listeners) {
listener.onChange();
}
}
}
最初のバージョンとほぼ同じようにする必要がありますが、コピーは少なくなります。リスナーの追加/削除は頻繁に行う操作ではないため、コピーも頻繁に行うべきではありません。
私が通常使用するバージョンは、次のように使用されるConcurrentHashMap
状態に基づいています。.keySet()
Set
ビューのイテレータは、ConcurrentModificationException を決してスローしない「弱い一貫性のある」イテレータであり、イテレータの構築時に存在していた要素をトラバースすることを保証し、構築後の変更を反映する場合があります (ただし保証はされません)。
つまり、反復には少なくとも、反復の開始時に登録されたすべてのリスナーが含まれ、反復中に追加された新しいリスナーも含まれる場合があります。削除されたリスナーについてはわかりません。私はこのバージョンが好きです。なぜなら、それはコピーではなく、実装が簡単だからCopyOnWriteArrayList
です。
class ObservableConcurrentSet {
private final Set<Listener> listeners = Collections.newSetFromMap(new ConcurrentHashMap<Listener, Boolean>());
public void addListener(Listener listener) {
listeners.add(listener);
}
public void removeListener(Listener listener) {
listeners.remove(listener);
}
protected void notifyChange() {
for (Listener listener : listeners) {
listener.onChange();
}
}
}
ベースの実装は良いアイデアですか、ConcurrentHashMap
それともここで何かを見落としましたか? それとももっとCollection
使いやすいものはありますか?