When implementing thread-safe listeners, I usually wonder what type of Collection best to hold listeners. So far I have found three options.
The Observable standard uses synchronized access to a simple ArrayList . Using a copy of the listeners is optional, but as far as I can tell, a good idea as it prevents issues like
- The Listener removes itself in the callback (
ConcurrentModificationException - can iterate through the indexed for loop in the reverse order to prevent this) - Executing external code inside a synchronized block can block everything.
The implementation, unfortunately, is more than a few lines. Collections.synchronizedList() will not require synchronized in removeListener , but it really is not worth it.
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()]); }
But there is a Collection in java.util.concurrent that is thread safe and potentially more efficient in its essence, as I would suggest that their internal locking mechanism is better optimized than a simple synchronized block. Making a copy for each notification is also quite expensive.
Based on 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(); } } }
You should do something like the first version does, but with fewer copies. Adding / removing listeners is not a very frequent action, which means that copies should not be very frequent either.
I usually use a version based on ConcurrentHashMap that points to .keySet() , which is used here as Set here:
A view iterator is a “weakly matched” iterator that will never throw a ConcurrentModificationException and ensures that the elements intersect as they existed during the construction of the iterator, and can (but not guaranteed) reflect any changes after construction.
This means that the iteration includes at least every listener that was registered at the start of the iteration, and may even include new ones that were added during the iteration. Not sure if listeners have been deleted. I like this version because it is not copied and not just implemented as 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(); } } }
Is a ConcurrentHashMap based implementation a good idea or am I missing something? Or is there an even better Collection to use?