1212import java .util .Collections ;
1313import java .util .List ;
1414import java .util .concurrent .Semaphore ;
15- import java .util .concurrent .atomic .AtomicReference ;
1615
1716public class ListenerList {
1817 private static final List <ListenerList > allLists = new ArrayList <>();
@@ -99,9 +98,14 @@ private static class ListenerListInst {
9998 // Enum#values() performs a defensive copy for each call.
10099 // As we never modify the returned values array in this class, we can safely reuse it.
101100 private static final EventPriority [] EVENT_PRIORITY_VALUES = EventPriority .values ();
101+ private static final IEventListener [] NO_LISTENERS = new IEventListener [0 ];
102102
103- private boolean rebuild = true ;
104- private AtomicReference <IEventListener []> listeners = new AtomicReference <>();
103+ /**
104+ * A lazy-loaded cache of listeners for all priority levels and any phase tracking notifiers.
105+ * <p><code>null</code> indicates that the cache needs to be rebuilt.</p>
106+ * @see #getListeners()
107+ */
108+ private volatile @ Nullable IEventListener [] listeners = NO_LISTENERS ;
105109
106110 /** A lazy-loaded array of lists containing listeners for each priority level. */
107111 @ SuppressWarnings ("unchecked" )
@@ -130,7 +134,7 @@ public void dispose() {
130134 }
131135 writeLock .release ();
132136 parent = null ;
133- listeners = null ;
137+ listeners = NO_LISTENERS ;
134138 if (children != null )
135139 children .clear ();
136140 }
@@ -164,16 +168,19 @@ public ArrayList<IEventListener> getListeners(EventPriority priority) {
164168 * @return Array containing listeners
165169 */
166170 public IEventListener [] getListeners () {
167- if (shouldRebuild ()) buildCache ();
168- return listeners .get ();
171+ var listeners = this .listeners ;
172+ if (listeners != null )
173+ return listeners ;
174+
175+ return buildCache ();
169176 }
170177
171178 protected boolean shouldRebuild () {
172- return rebuild ; // || (parent != null && parent.shouldRebuild()) ;
179+ return this . listeners == null ;
173180 }
174181
175182 protected void forceRebuild () {
176- this .rebuild = true ;
183+ this .listeners = null ;
177184 if (this .children != null ) {
178185 synchronized (this .children ) {
179186 for (ListenerListInst child : this .children )
@@ -189,9 +196,15 @@ private void addChild(ListenerListInst child) {
189196 }
190197
191198 /**
192- * Rebuild the local Array of listeners, returns early if there is no work to do.
199+ * Rebuilds the cache of listeners, setting the {@link #listeners} field to the new array.
200+ *
201+ * <p>
202+ * Important: To avoid a race condition, you must use the return value of this method as the source of truth.
203+ * Attempting to read the {@link #listeners} field immediately after calling this method may observe
204+ * unexpected results caused by concurrent calls to this method and/or {@link #forceRebuild()}.
205+ * </p>
193206 */
194- private void buildCache () {
207+ private IEventListener [] buildCache () {
195208 if (parent != null && parent .shouldRebuild ())
196209 parent .buildCache ();
197210
@@ -202,8 +215,10 @@ private void buildCache() {
202215 ret .add (value ); // Add the priority to notify the event of its current phase.
203216 ret .addAll (listeners );
204217 }
205- this .listeners .set (ret .toArray (new IEventListener [0 ]));
206- rebuild = false ;
218+
219+ var retArray = ret .isEmpty () ? NO_LISTENERS : ret .toArray (new IEventListener [0 ]);
220+ this .listeners = retArray ;
221+ return retArray ;
207222 }
208223
209224 public void register (EventPriority priority , IEventListener listener ) {
0 commit comments