При внедрении потокобезопасных прослушивателей я обычно задаюсь вопросом, какой тип 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
?
CopyOnWrites идеально подходят для этого. Почему бы не использовать их? – user949300
@Gray действительно означает, что добавление/удаление может привести к тому, что некоторые другие слушатели не будут видны во время итерации? Как и раньше, '{a, b, c}', добавляя 'd', пока итерация и итерация будут видеть только' {a, b, d} '. Или это гарантирует, что элементы, которые не касаются, видны во время итерации. Не замечание тех, которые касаются во время итерации, прекрасны. – zapl
Нет, никогда. Вы можете не видеть 'd' в итерации, но вы всегда будете видеть элементы, которые существовали, когда итератор был создан, если вы не удалите их. – Gray