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.
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.
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
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.
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) {
}
}
|
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.
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.
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();
}
}
|
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.
- Participate in the discussion forum.
- Read the complete Java theory and practice series by Brian Goetz. Installments of particular relevance to this article include "Safe construction techniques" (developerWorks, June 2002) and "Hey, where'd my thread go?"
(developerWorks, September 2002).
- The Observer pattern is described in the Gang of Four's book Design Patterns (Addison-Wesley, 1995).
- Learn how to use memory profilers to spot problems caused by lapsed listeners in this article from OTN.
- Bruce Tate describes the lapsed listener problem and a wealth of other common errors in
"A taste of bitter Java"
(developerWorks, March 2002).
- To learn more about Java technology, visit the developerWorks Java zone. You'll find technical
documentation, how-to articles,
education, downloads, product information, and more.
- Visit the New to Java
technology page for the latest resources to help you get started with Java
programming.
- Get involved in the developerWorks community by
participating in
developerWorks blogs.
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.



