 | Level: Introductory Brian Goetz (brian@quiotix.com), Principal Consultant, Quiotix
26 Jul 2005 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. 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
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 - 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.
About the author  | |  | 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. |
Rate this page
|  |