'Is Apache EventListenerSupport thread safe?

Summary

The org.apache.commons.lang3.event.EventListenerSupport is used in order to fire when some class performs a certain operation and wants to inform all listeners about it.

This class is however accessed by multiple threads and is already forced to synchronize on other lists it processes, so that a ConcurrentModificationException is avoided.

Different threads are responsible for adding, removing and firing events. The functions for this purpose look like this:

    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();
    }

Since I already ran into concurrency issues with the class I anticipate simultaneous access to the EventListenerSupport myEventSupport. This is both reading and writing access.

Question

I was unable to find anything on synchronization and thread-safety in the Apache documentation: https://commons.apache.org/proper/commons-lang/javadocs/api-3.8/org/apache/commons/lang3/event/EventListenerSupport.html

However I did find this comment in the description of the .class file itself:

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<>();

    // [...]
}

Is it therefore safe to assume, concurrent access to addListener, removeListener, as well as fire is handled by EventListenerSupport?



Solution 1:[1]

I am no expert in this but for me it seems like the following:

The call to fire uses the internal proxy to ultimately call the following loop:

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

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

And the CopyOnWriteArrayList used is documented as follows:

A thread-safe variant of ArrayList in which all mutative operations (add, set, and so on) are implemented by making a fresh copy of the underlying array.

This is ordinarily too costly, but may be more efficient than alternatives when traversal operations vastly outnumber mutations, and is useful when you cannot or don't want to synchronize traversals, yet need to preclude interference among concurrent threads.

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

So I basically see the above iteration and see the iterate safely in the documentation.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1