#java #multithreading #concurrency #apache-commons
#java #многопоточность #параллелизм #apache-commons
Вопрос:
Краткие сведения
Используется org.apache.commons.lang3.event.EventListenerSupport
для запуска, когда какой-либо класс выполняет определенную операцию и хочет сообщить об этом всем слушателям.
Однако к этому классу обращаются несколько потоков, и он уже вынужден синхронизироваться с другими списками, которые он обрабатывает, чтобы ConcurrentModificationException
избежать a .
Разные потоки отвечают за добавление, удаление и запуск событий. Функции для этой цели выглядят следующим образом:
public void addListener( MyListener listener )
{
myEventSupport.addListener( listener );
}
public void removeListener( MyListener listener )
{
myEventSupport.removeListener( listener );
}
public void someEvent()
{
// do other stuff
myEventSupport.fire().updateIssued();
}
Поскольку я уже сталкивался с проблемами параллелизма с классом, я ожидаю одновременного доступа к EventListenerSupport myEventSupport
. Это доступ как для чтения, так и для записи.
Вопрос
Я не смог найти ничего о синхронизации и потокобезопасности в документации Apache: https://commons.apache.org/proper/commons-lang/javadocs/api-3.8/org/apache/commons/lang3/event/EventListenerSupport.html
Однако я нашел этот комментарий в описании самого .class
файла:
public class EventListenerSupport<L> implements Serializable {
// [...]
/**
* The list used to hold the registered listeners. This list is
* intentionally a thread-safe copy-on-write-array so that traversals over
* the list of listeners will be atomic.
*/
private List<L> listeners = new CopyOnWriteArrayList<>();
// [...]
}
Поэтому можно ли предположить, что одновременный доступ к addListener
, removeListener
, а также fire
обрабатывается EventListenerSupport
?
Ответ №1:
Я не эксперт в этом, но для меня это выглядит следующим образом:
Вызов fire
использует внутренний прокси-сервер для окончательного вызова следующего цикла:
public Object invoke(final Object unusedProxy, final Method method, final Object[] args) throws Throwable {
for (final L listener : listeners) {
method.invoke(listener, args);
}
return null;
}
И CopyOnWriteArrayList
используемый документируется следующим образом:
Потокобезопасный вариант ArrayList, в котором все изменяемые операции (add, set и т. Д.) Реализуются путем создания новой копии базового массива.
Обычно это слишком дорого, но может быть более эффективным, чем альтернативы, когда количество операций обхода значительно превышает количество мутаций, и полезно, когда вы не можете или не хотите синхронизировать обходы, но при этом необходимо исключить вмешательство между параллельными потоками.
https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CopyOnWriteArrayList.html
Итак, я в основном вижу приведенную выше итерацию и вижу безопасную итерацию в документации.