2013-09-23 3 views
4

При внедрении потокобезопасных прослушивателей я обычно задаюсь вопросом, какой тип Collection является лучшим для прослушивания. На данный момент я нашел три варианта.Воспроизведение/Наблюдаемая реализация, синхронизированная и параллельная коллекции

В стандарте Observable используется synchronized доступ к простому ArrayList. Использование копии слушателей не является обязательным, но, насколько я могу сказать, хорошая идея, так как это предотвращает проблемы, как

  • Listener удаляет себя в функции обратного вызова (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 s в 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?

+0

CopyOnWrites идеально подходят для этого. Почему бы не использовать их? – user949300

+0

@Gray действительно означает, что добавление/удаление может привести к тому, что некоторые другие слушатели не будут видны во время итерации? Как и раньше, '{a, b, c}', добавляя 'd', пока итерация и итерация будут видеть только' {a, b, d} '. Или это гарантирует, что элементы, которые не касаются, видны во время итерации. Не замечание тех, которые касаются во время итерации, прекрасны. – zapl

+0

Нет, никогда. Вы можете не видеть 'd' в итерации, но вы всегда будете видеть элементы, которые существовали, когда итератор был создан, если вы не удалите их. – Gray

ответ

2

Является ли реализация ConcurrentHashMap хорошей идеей или я что-то упустил? Или есть еще лучшая коллекция для использования?

ConcurrentHashMap, как представляется, в этом случае. Это, безусловно, лучше, чем использование Collections.synchronizedList(). Итератор CHM может видеть или не видеть новые записи на карте, если они добавлены, когда итератор идет по карте. Он также может видеть или не видеть старые записи, если они удаляются во время итерации. Оба они обусловлены условиями гонки, когда добавляемые/удаленные узлы были размещены и где расположен итератор. Но итератор всегда будет видеть элементы, которые были на карте, когда она начиналась до тех пор, пока они не были удалены.

+0

Хотя ConcurrentHashMap является разумным, почему бы не создать коллекцию CopyOnWrite? Из того, что я вижу, они отлично подходят для этой задачи. – user949300

+0

Я думаю, что он может работать, но в многопоточной среде, почему бы не использовать CHP? Если будет немного обновлений, то COW будет успешно выигрывать, но он не будет хорошо масштабироваться. Я подозреваю @ user949300. – Gray

+0

Я считаю, что COW будет иметь значительно лучшую производительность для чтения, так как в основном не требуется блокировка. И, например, для типа Listener, чтение (запуск событий), как правило, намного чаще, чем записи (добавление слушателя). – user949300

2

Существует шаблон, который превосходит все эти варианты начиная с Java 1.1. Посмотрите на класс AWTEventMulticaster и как он работает. Он обеспечивает безопасность потоков благодаря неизменности и, следовательно, не создает дополнительных накладных расходов. Он даже обеспечивает наиболее эффективный способ обработки дела, когда нет или точно одного слушателя. Ну, я думаю, что он предлагает самую эффективную доставку событий в целом.

См http://docs.oracle.com/javase/7/docs/api/java/awt/AWTEventMulticaster.html

Посмотрите в исходный код этого класса, если вы хотите знать, как реализовать такой шаблон для собственных типов событий.

Жаль, что разработчик Swing не знал об этом и создал этот ужасный EventListenerList ведущих разработчиков по неправильному пути.

И, кстати, этот шаблон также решает проблему, которую слушатели, добавленные во время доставки события, не должны видеть текущие события (ы), которые произошли до добавления. Бесплатно. Так как Java 1.1

+0

Интересное .. динамическое дерево 'AWTEventMulticaster' с слушателями как листовые узлы. Я буду копаться в этом и посмотреть, могу ли я использовать это – zapl

+0

Есть два недостатка. A) вы можете добавлять слушателей не один раз. Не большая проблема (если вообще), и проверка может быть добавлена. B) Это непросто использовать в качестве общего шаблона, поскольку многопользовательский узел, который пересылает события своим дочерним, должен расширять интерфейс прослушивателя, чтобы он был взаимозаменяемым с простым слушателем. Для универсальной реализации для некоторого Listener 'T' для этого потребуется« класс EventMulticaster для реализации T ». Должно быть выполнимо через [reflection] (http://docs.oracle.com/javase/7/docs/api/java/lang/reflect/Proxy.html), может быть, с помощью тонкой реализации каждый раз. – zapl

+0

В принципе такой мультикастер можно было бы сгенерировать на лету, подобно тому, как работает «java.lang.reflect.Proxy». Однако, как правило, тип события известен во время компиляции, а количество типов слушателей является конечным. Поэтому нет необходимости создавать статический статический файл. Было бы возможно создать макрос редактора, если это заслуживает количество типов слушателей. – Holger

Смежные вопросы