Flexible Event Delivery with Executors
Pages: 1, 2
Useful Executors
The Dispatchable Event Library derives its power from the range of available Executors
that can be used to customize event delivery. We will look at two available
groups of Executors:
DispatchableEvent library Executors
The Dispatchable Event Library includes the org.recoil.pixel.executor package:
DirectExecutor-
DirectExecutorcalls the code provided to it synchronously in the sameThreadthat callsexecute. If you use this class withDispatchableEventSupportyou simply get the regular listener behavior, which makes it a useful default. AWTExecutor-
AWTExecutorschedules code on the AWT event dispatch thread. Events are interleaved withAWTEvents likeMouseEventandActionEvent. Therefore, listeners called by thisExecutorare free to call methods that update AWT and Swing GUI components without usingSwingUtilities.invokeLater(), because they are already called on the correctThread. MIDPExecutor-
MIDPExecutoris the equivalent ofAWTExecutorin the J2ME MIDlet world. It ensures that your events are delivered using thecallSeriallymethod required for interacting with the MIDlet's GUI.
For example, to use an AWTExecutor to deliver ClipEvents on
the AWT event dispatch thread:
import org.recoil.pixel.dispatchable.*;
import org.recoil.pixel.executors.*;
Executor e = new AWTExecutor();
DispatchableEventSupport<ClipListener> d =
new DispatchableEventSupport<ClipListener>(e);
J2SE 5.0--Built-In Executors
The new J2SE 5.0 class java.util.concurrent.Executors is
used to create sophisticated thread pools. You can configure the number of
Threads in the pool, set delays or employ periodic scheduling.
For example, using a J2SE
5.0 Executor to provide a pool of five event delivery threads:
import org.recoil.pixel.dispatchable.*;
import java.util.concurrent.*;
Executor tp = Executors.newFixedThreadPool(5);
DispatchableEventSupport<ClipListener> d = new
DispatchableEventSupport<ClipListener>(tp);
J2EE does not yet have a standard thread pool facility, but an Executor could
be implemented using JMS and message-driven beans to provide configurable event
delivery.
Problem Solving
Now we have seen the Dispatchable Event Library and some example Executors,
we can look at how to use these tools to avoid common listener problems.
Avoiding Starvation
To help you avoid listener starvation problems, DispatchableEventSupport provides
two addListener methods:
public void addListener(L listener);
public void addListener(L listener,
Executor executor);
Listeners added with addListener(L listener) share the default Executor set
when the DispatchableEventSupport was created. The second addListener(L
listener, Executor executor) method associates a custom Executor with
its listener parameter.
Not only does this provide a great way for users of your component to customize
event delivery, but you can help them isolate their listeners by exposing only
the two argument addListener:
import org.recoil.pixel.dispatchable.*;
public class SharedComponent {
DispatchableEventSupport<ClipListener> d =
new DispatchableEventSupport<ClipListener>();
public void
addListener(ClipListener l, Executor e) {
d.addListener(l, e);
}
public void fireEvent(ClipEvent e) {
d.fireEvent(new DispatchableEvent
<ClipListener, ClipEvent>(e) {
public void
dispatch( ClipListener l, ClipEvent ce) {
l.clipUpdate(ce);
}
});
}
[...]
}
Developers attaching listeners to SharedComponent are forced
to specify an Executor for each listener. Assuming each developer
keeps her Executor private, her listeners have a measure of isolation.
This is especially powerful if she uses a thread-pool-based Executor.
SharedComponent is sufficient if all relevant code is under your
team's control, but it does not fully eliminate the starvation problem. If you
are forced to support badly behaved listeners in legacy or third-party code,
you can increase the isolation by taking control and forcing each listener
to have its own private Executor:
import org.recoil.pixel.dispatchable.*;
import java.util.concurrent.*;
public class DefensiveComponent {
private final
DispatchableEventSupport<ClipListener> d =
new DispatchableEventSupport<ClipListener>();
public void addListener(ClipListener l) {
Executor e=Executors.newSingleThreadExecutor();
d.addListener(l, e);
}
public void removeListener(ClipListener l) {
d.removeFirstInstanceOfListener(l);
}
public void fireEvent(ClipEvent e) {
d.fireEvent(new DispatchableEvent
<ClipListener, ClipEvent>(e) {
public void
dispatch( ClipListener l, ClipEvent ce) {
l.clipUpdate(ce);
}
});
}
[...]
}
|
Related Reading
Java Threads |
DefensiveComponent assigns a dedicated event delivery Thread to
every listener added. This isolates it from poorly behaved listeners and ensures
the listeners can proceed independently; faster listeners need not wait for
slower ones. This strategy is simple and safe, but expensive, because it must
create and clean up a lot of Threads. In most cases, an appropriately
sized thread pool created using Executors provides a balance of
isolation and cost.
Synchronizing Multiple Event Streams
DispatchableEvent allows you to synchronize events from different asynchronous
sources by multiplexing their events through a single Executor.
For example, consider a multi-modal drawing application that supports a mouse and speech recognition.
Typically, a speech recognizer dispatches an event when a speech fragment is
recognized. Imagine the user selects a shape with the mouse and then says, "Delete."
Clearly it is desirable that the mouse event is handled first (to select the
correct shape) before the speech event is processed, otherwise the wrong item
could be deleted. The easy way to do this is to deliver speech events using
an AWTExecutor, which places the speech events in the AWT event
queue as they are received, ensuring they are interleaved with the MouseEvents
appropriately.
This idea can be expanded to as many asynchronous event streams as necessary
by providing each source of events a reference to a shared, queue-backed Executor.
Each source places events in the queue in the order in which they occur, interleaving
their delivery.
Conclusion
This article highlighted the problems that can occur with the listener paradigm.
We saw how a simple dispatcher library can be used to customize event delivery
using Executors. Using different strategies you can integrate
your component with a subsystem like AWT, you can give your clients more choice
by allowing them to specify the Executor used, or you can go the
other way and isolate badly behaved listeners to prevent starvation.
Resources
- The Dispatchable Event Library
- J2SE 5.0's
Executorclass - J2SE-1.4-compatible back-ported
java.util.concurrentlibrary - J2SE 5.0 Concurrency Utilities guide
Andrew Thompson has been working with Java technology for nine years, and currently works for Finetix LLC.
Return to ONJava.com.