CopyOnWriteArrayList publishes a fresh array when it changes. Iterators hold a snapshot, so registration changes do not disrupt an in-flight listener dispatch.
Java CopyOnWriteArrayList: dispatch against a listener snapshot
Operational contract
The registry fits a read-heavy system with infrequent listener changes. addIfAbsent avoids duplicate registration by equality. A dispatch gets the iterator view available when iteration begins; removing a listener during dispatch does not retract a callback already present in that snapshot. Callback failure is not rolled back and this sample deliberately lets it propagate to the caller. In a production event bus, failure isolation and delivery guarantees need their own policy.
Failure case
A warehouse adds one audit listener while 47 shipment notices are being dispatched. An in-progress traversal keeps the listeners it started with. The next dispatch can see the new registration. This makes iteration predictable, but it does not promise that a removal stops an already-started callback.
Java code
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Consumer;
public class ShipmentNoticeListeners {
private final CopyOnWriteArrayList<Consumer<String>> listeners = new CopyOnWriteArrayList<>();
public void register(Consumer<String> listener) {
listeners.addIfAbsent(listener);
}
public void remove(Consumer<String> listener) {
listeners.remove(listener);
}
public void dispatch(String shipmentId) {
for (Consumer<String> listener : listeners) listener.accept(shipmentId);
}
}Performance and ownership cost
A mutation copies the backing array, so it costs O(L) time and extra O(L) array memory for L listeners. A dispatch visits O(L) callbacks and its iterator can retain an old array until the traversal ends. High-churn registration is the wrong workload for this structure.
Common Mistakes
- Do not choose copy-on-write storage when writes dominate.
- Do not assume removal cancels callbacks already captured by an iterator.
- Do not confuse snapshot iteration with transactional callback delivery.
Connected lessons
- Java Iterator and ListIterator: traversal and controlled edits
- Java ConcurrentHashMap: atomic updates and weakly consistent reads
- Java AtomicReference: publish one immutable map snapshot
- Java ConcurrentHashMap.computeIfAbsent: create a counter once per key
- Java ConcurrentSkipListMap: inspect an ordered time window
- Java concurrent state and handoff quiz
- Advanced Java
Continue with: Java CopyOnWriteArraySet: publish against a stable listener snapshot.
