Java theory and practice: Be a good (event) listener

Guidelines for writing and supporting event listeners

The Observer pattern, most often seen in Swing development, is also very useful for decoupling components in situations other than GUI applications. However, some common pitfalls with registering or invoking listeners exist. In this installment of Java theory and practice, Java therapist Brian Goetz offers some feel-good advice on how to be a good listener -- and how to be nice to your listeners, too.

Brian Goetz (brian@quiotix.com), Principal Consultant, Quiotix

Brian Goetz has been a professional software developer for over 18 years. He is a Principal Consultant at Quiotix, a software development and consulting firm located in Los Altos, California, and he serves on several JCP Expert Groups. See Brian's published and upcoming articles in popular industry publications.



26 July 2005

Also available in Russian Japanese

The Swing framework makes extensive use of the Observer pattern (also known as the publish-subscribe pattern) in the form of event listeners. Swing components that are the targets of user interaction fire events when the user interacts with them; data model classes fire events when the data has changed. The use of Observer in this way lets the controller be separated from the model, and the model be separated from the view, simplifying the development of GUI applications.

The "Gang of Four" Design Patterns book (see Resources) describes the Observer pattern as defining "a one-to-many dependency between objects so that when one object changes state, all of its dependents are notified and updated automatically." The Observer pattern enables loose coupling of components; components can keep their states synchronized without necessarily needing direct knowledge of each other's identities or internals, facilitating component reuse.

AWT and Swing components, such as JButton or JTable, use the Observer pattern to decouple the generation of GUI events from their semantics within a given application. Similarly, the Swing model classes, such as TableModel and TreeModel, use Observer to decouple data model representation from view generation, which enables multiple, independent views of the same data. Swing defines a hierarchy of Event and EventListener objects; components that can generate events, such as JButton (visual component) or TableModel (data model) provide addXxxListener() and removeXxxListener() methods for registering and unregistering listeners. These classes decide when they need to fire events, and when they do, they call all the listeners that are registered.

To support listeners, an object needs to maintain a list of registered listeners, provide a means of registering and unregistering listeners, and call each listener when the appropriate event occurs. Listeners are easy to use and support (not just in GUI applications), but a couple of pitfalls should be avoided on both sides of the registration interface: the components that support listeners and the components that register them.

Thread-safety issues

Frequently, listeners are called in a different thread from which they were registered. To support registering listeners from different threads, whatever mechanism is used to store and manage the list of active listeners must be thread-safe. Many of the examples in the Sun documentation use Vector for storing the list of listeners, which addresses part of the problem, but it doesn't address the whole problem. When an event is fired, the component firing it will want to iterate the list of listeners and call each one, which introduces the risk of concurrent modification if some thread happens to want to add or remove a listener while the list of listeners is being iterated.

Managing the listener list

Suppose you use a Vector<Listener> to store your list of listeners. While the Vector class is thread-safe, which means that its methods can be called without additional synchronization without risk of corrupting the Vector data structures, iterating a collection involves "check-then-act" sequences, which are at risk of failing if the collection is modified during iteration. Let's say there are three listeners in your list at the start of iteration. As you iterate through the Vector, you repeatedly call size() and get() until there are no more elements to retrieve, as in Listing 1:

Listing 1. Unsafe iteration of a Vector
Vector<Listener> v;
for (int i=0; i<v.size(); i++)
  v.get(i).eventHappened(event);

But what happens if, just after you call Vector.size() for the last time, someone removes a listener from the list? Now, Vector.get() will return null (which is correct, because the state of the vector has changed since you last checked), and you will throw a NullPointerException when you try to call eventHappened(). This is an example of a check-then-act sequence -- you check to see if any more elements exist, and if so, you get the next element -- but in the presence of concurrent modification, the state could have changed since the check. Figure 1 illustrates the problem:

Figure 1. Concurrent iteration and modification, resulting in unexpected failure
Concurrent iteration and modification, resulting in unexpected failure

One solution to this problem is to hold the lock on the Vector for the duration of iteration; another is to clone the Vector or call its toArray() method to retrieve its contents every time an event occurs. Both of these approaches have performance consequences; the first risks locking out other threads that might want to access the listener list for the duration of iteration, while the second involves creating a temporary object and copying the list every time an event occurs.

If you use an Iterator to traverse the listener list, you'll have the same problem in a slightly different guise; rather than throwing NullPointerException, the iterator() implementation will throw ConcurrentModificationException if it detects that the collection has been modified since iteration began. Again, this can be prevented by locking the collection for the duration of iteration.

The CopyOnWriteArrayList class, in java.util.concurrent, can help prevent this problem. It implements List and is thread-safe, but its iterators will not throw ConcurrentModificationException and do not require any additional locking during traversal. This combination of features is achieved by reallocating and copying the list contents internally every time the list is modified, so that threads iterating the contents do not have to deal with changes -- from their perspective, the list contents remain constant during iteration. While this may sound inefficient, remember that in most Observer situations, each component has a small number of listeners, and insertions and removal are greatly outnumbered by traversals. So the faster iteration makes up for the slower mutation and provides better concurrency because multiple threads can iterate the list simultaneously.

Initialization safety risks

It is tempting to register a listener from its constructor, but it's a temptation you should avoid. Not only does it court the "lapsed listener" problem (which I discuss in a moment), but it creates several thread-safety problems. Listing 2 shows a harmless looking attempt to construct and register a listener at the same time. The problem is that it lets the "this" reference to the object escape before it is fully constructed. It may look harmless, as the registration is the last thing that the constructor does, but looks can be deceiving:

Listing 2. Event listener that lets the "this" reference escape, causing trouble
public class EventListener { 

  public EventListener(EventSource eventSource) {
    // do our initialization
    ...

    // register ourselves with the event source
    eventSource.registerListener(this);
  }

  public onEvent(Event e) { 
    // handle the event
  }
}

One risk of this approach emerges when the event listener is subclassed: Now, anything done by the subclass constructor happens after the EventListener constructor runs, and therefore after the EventListener has been published, creating race conditions. With some unlucky timing, the onEvent method in Listing 3 can get called before the list field is initialized, causing a very confusing NullPointerException when dereferencing a final field:

Listing 3. Trouble caused by subclassing the EventListener class in Listing 2
public class RecordingEventListener extends EventListener {
  private final ArrayList<Event> list;

  public RecordingEventListener(EventSource eventSource) {
    super(eventSource);
    list = Collections.synchronizedList(new ArrayList<Event>());
  }

  public onEvent(Event e) { 
    list.add(e);
    super.onEvent(e);
  }
}

Even if your listener class is final and therefore cannot be subclassed, you should still not allow the "this" reference to escape from the constructor -- doing so undermines some safety guarantees provided by the Java Memory Model. It is possible to let the "this" reference escape without the word "this" appearing in your program; publishing a nonstatic inner class instance has the same effect, because an inner class holds a reference to its enclosing object's "this" reference. One of the most common causes for accidentally allowing the "this" reference to escape is registering listeners, as shown in Listing 4. Event listeners should not be registered from constructors!

Listing 4. Implicitly allowing the "this" reference to escape by publishing an inner class instance
public class EventListener2 {
  public EventListener2(EventSource eventSource) {

    eventSource.registerListener(
      new EventListener() {
        public void onEvent(Event e) { 
          eventReceived(e);
        }
      });
  }

  public void eventReceived(Event e) {
  }
}

Listener thread-safety

A third thread-safety issue raised by the use of listeners stems from the fact that listeners may want to access application data, and the listener is usually called in a thread that is not directly under the control of the application. If you register a listener with a JButton or other Swing component, it will be called from the EDT. Listener code can safely call methods on Swing components from the EDT, but accessing application objects from the listener could add new thread-safety requirements to your program if those objects are not already thread-safe.

The Swing components generate events as a result of user interactions, but the Swing model classes generate events when the fireXxxEvent() methods are called. These methods will in turn call the listeners in whatever thread they were called. Because the Swing model classes are not thread-safe and are supposed to be confined to the EDT, any calls to fireXxxEvent() should also be performed from the EDT. If you want to trigger an event from another thread, you should use the Swing invokeLater() facility to cause the method to be called in the EDT instead. In general, be aware of what thread event listeners will be called from, and ensure that any objects they touch are either thread-safe or protected with the appropriate synchronization (or thread-confinement, as with Swing model classes) everywhere they are accessed.


Lapsed listeners

Whenever you use the Observer pattern, you are coupling two separate components -- the observer and the observed, which generally have distinct lifecycles. One consequence of registering a listener is that it creates a strong reference from the observed object to the listener -- which prevents the listener (and any objects it references) from being garbage collected until the listener is unregistered. In many cases, the listener lifecycle is intended to be at least as long as the observed component -- many listeners persist for the entirety of the application duration. But in some cases, listeners that were intended to be short-lived end up being permanent, with the only evidence of their unintended lingering being slower application performance and higher-than-necessary memory usage.

The "lapsed listener" problem can be caused by carelessness at the design level: not adequately considering the lifetimes of the objects involved or simply by sloppy coding. Listener registration and unregistration should always be done in matched pairs. But even when you do so, you must also ensure that the unregistration actually executes at the right time. Listing 5 shows an example of a coding idiom at risk for lapsed listeners. It registers a listener with a component, performs some actions, and then unregisters the listener:

Listing 5. Code at risk for lapsed listeners
  public void processFile(String filename) throws IOException {
    cancelButton.registerListener(this);
    // open file, read it, process it
    // might throw IOException
    cancelButton.unregisterListener(this);
  }

The problem with Listing 5 is that if the file-processing code throws an IOException -- a perfectly realistic possibility -- the listener is never unregistered, which means it will never get garbage collected. The unregister operation should be done in a finally block so that it is executed in all paths out of the processFile() method.

One approach that is sometimes suggested for dealing with lapsed listeners is to use weak references. While this approach is possible, it's fairly tricky to implement. For it to work, you need to find another object whose lifecycle is exactly the lifecycle of your listener and arrange for it to hold the strong reference to your listener, which is not always easy.

Another technique that can sometimes be used to find otherwise hidden lapsed listeners is preventing a given listener object from being registered twice with a given event source. This situation is generally indicative of a bug -- that a listener was registered but not unregistered, and then was registered again. One way to mitigate the effects of this without necessarily detecting the problem is to use a Set instead of a List for storing listeners; alternately, you could check the List for whether the listener is registered before registering it, and throw an exception (or log an error) if it is, so that evidence of the coding error can be gathered and acted on.


Other listener transgressions

When writing listeners, you should always be aware of the environment in which they will execute. Not only must you pay attention to thread-safety issues, but you need to remember that a listener can mess things up for its caller in other ways, too. One thing that a listener should not do is block for any perceptible amount of time; it has likely been called from an execution context that is expecting to get control back quickly. If a listener is going to perform a potentially time-consuming operation like processing a large document or doing something that may block, such as performing socket IO, it should arrange to do that work in another thread so it can return to its caller quickly.

Another way that listeners can make trouble for unwary event sources is throwing an unchecked exception. While most of the time, we never intend to throw unchecked exceptions, it does sometimes happen anyway. If you used the idiom in Listing 1 to invoke listeners, and the second listener in the list throws an unchecked exception, not only do the subsequent listeners not get called (potentially leaving the application in an inconsistent state), but it might even take down the thread in which it is executing, causing a partial application failure.

When invoking unknown code (which listeners certainly qualify as), it is prudent to execute it in a try-catch block so that ill-behaved listeners do not do more damage than necessary. You might even want to automatically unregister a listener that throws an unchecked exception; after all, this is evidence that the listener is broken. (You would also want to log this or otherwise bring it to the user's attention so the user can figure out why the program stopped working as expected.) Listing 6 shows an example of such an approach, which nests the try-catch block inside the iteration loop:

Listing 6. Robust listener invocation
List<Listener> list;
for (Iterator<Listener> i=list.iterator; i.hasNext(); ) {
    Listener l = i.next();
    try {
        l.eventHappened(event);
    }
    catch (RuntimeException e) {
        log("Unexpected exception in listener", e);
        i.remove();
    }
}

Conclusion

The Observer pattern is quite useful for creating loosely coupled components and encouraging component reuse, but it has several risks that both listener writers and component writers should watch out for. When registering a listener, always be aware of the listener's lifecycle. If it is intended to have a shorter lifetime than the application, ensure that it gets unregistered, so that it can be garbage collected. When writing listeners and components, be aware of the thread-safety issues involved. Any objects touched by listeners should either be thread safe or, for thread-confined objects such as Swing models, the listener should be confident that it is executing in the right thread.

Resources

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Java technology on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java technology
ArticleID=89234
ArticleTitle=Java theory and practice: Be a good (event) listener
publish-date=07262005