Является ли потокобезопасным Apache EventListenerSupport?

#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;
}
 

См. https://github.com/apache/commons-lang/blob/6fa0a175959bd4283ef46d1d406d9ffcc6c08a9a/src/main/java/org/apache/commons/lang3/event/EventListenerSupport.java#L327

И CopyOnWriteArrayList используемый документируется следующим образом:

Потокобезопасный вариант ArrayList, в котором все изменяемые операции (add, set и т. Д.) Реализуются путем создания новой копии базового массива.

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

https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CopyOnWriteArrayList.html

Итак, я в основном вижу приведенную выше итерацию и вижу безопасную итерацию в документации.