ONJava.com    
 Published on ONJava.com (http://www.onjava.com/)
 See this if you're having trouble printing code examples


Flexible Event Delivery with Executors

by Andrew Thompson
03/23/2005

Every Java programmer is familiar with the EventListener pattern of asynchronous event delivery. Most have also written the boilerplate code needed to manage listeners and deliver events to other components. Listeners are simple, familiar, flexible, and easy to implement, but they call into code written by other developers, which can cause problems:

This article uses Doug Lea's Executor (now part of J2SE 5.0) to make event dispatch more flexible. You can use the ideas presented to:

A note on Java 1.4 compatibility: This article uses J2SE 5.0 generic types to eliminate casting of events and listeners. The Dispatchable Event Library also contains a non-generic version of the classes that will run on JDK 1.4. Older JDKs lack a built-in java.util.concurrent package, but fortunately you can download a compatible back-ported version.

Standard Listeners

For the examples in this article, imagine writing a video editing system that uses a ClipEvent class to report video clip events (e.g., started, paused, etc.) and a ClipListener interface implemented by components that respond to the events.

import java.util.EventListener;

class ClipEvent extends EventObject {
    //...
}

public interface ClipListener 
extends EventListener {
  public void clipUpdate(ClipEvent e);
}

In many programs, events are created by some kind of controller class that is also responsible for managing a list of interested listeners and dispatching each event that occurs to the listeners. Often, it is possible to split this responsibility and delegate listener management to a simple helper class called a dispatcher. ClipEventDispatcher illustrates this style of event dispatch:

import java.util.*;
public class ClipEventDispatcher {
    
  final Collection listeners = new ArrayList();
    
  public synchronized void 
  addListener(ClipListener l) {
    listeners.add(l);
  }

  public synchronized void 
  removeListener(ClipListener l) {
    listeners.remove(l);
  }

  public void fireEvent(ClipEvent e) {
    for(Iterator i = copyListeners(); i.hasNext();) {
      ClipListener l = (ClipListener) i.next();
      l.clipUpdate(e);
    }
  }

  private synchronized Iterator copyListeners() {
    return new ArrayList(listeners).iterator();
  }    
}

ClipEventDispatcher exhibits the typical event delivery problems discussed in the introduction. If any ClipListener has a slow implementation of the clipUpdate method the other listeners starve. The dispatcher's author decides which Thread will call fireEvent; there is no way for the developer of a ClipListener to customize event delivery.

Flexible Task Execution in Java: The Executor Interface

J2SE 5.0 standardizes the java.util.concurrent package, which includes the Executor interface originally created by Doug Lea. Executors run tasks that implement the java.lang.Runnable interface. You are probably familiar with using Runnable tasks in conjunction with java.lang.Thread:

class MyCoolTask implements Runnable {
  public void run() {
    //... do useful stuff
  }
}
Thread t = new Thread(new MyCoolTask());
t.start();

Using Runnables with an Executor is similar:

Executor e = ...
e.execute(new MyCoolTask());               

Runnable tasks passed to the execute method will have their run method called by the Executor. Unlike a Thread, which can only have its start method called once, Executors can typically run as many Runnable tasks as you need. Different Executors embody different strategies for executing the tasks. For example, J2SE 5.0 provides an Executor that acts as a scheduler, meaning it calls the run method of a task after a configurable time delay. The Useful Executors section on the next page describes several Executors in more detail, but first we will learn how to apply them to our event dispatch problems.

Adding Flexibility with DispatchableEvent

To make it easy to combine Executors and events I have developed a Dispatchable Event Library that offers a helper class, DispatchableEventSupport, which provides listener management and event dispatch. Internally, a DispatchableEventSupport instance uses an Executor to fire events, and thus changing the Executor customizes the event delivery strategy.

Here is the ClipEventDispatcher example rewritten using the DispatchableEvent library:

import org.recoil.pixel.dispatchable.*;
import org.recoil.pixel.executor.*;

public class ClipEventDispatcher {
   Executor e = new DirectExecutor(); //[1]
  DispatchableEventSupport<ClipListener> d = 
    new DispatchableEventSupport<ClipListener>(e);
    
  public void addListener(ClipListener l) {
    d.addListener(l);
  }
  
  public void removeListener(ClipListener l) {
    d.removeListener(l);
  }
  
  public void fireEvent(ClipEvent e) {
    d.fireEvent(new DispatchableEvent
                 <ClipListener, ClipEvent>(e) {
      public void 
      dispatch( ClipListener l, ClipEvent ce) {
         l.clipUpdate(ce); //[2]
      }
    });
  }
}

At line [1] this example uses a DirectExecutor, which simply recreates the behavior of the original ClipEventDispatcher. The event delivery can be customized by varying the Executor used, either when the DispatchableEventSupport is created or when listeners are added.

Line [2] illustrates how little code you need to integrate with your application. The Dispatchable Event Library handles the mechanics of event dispatch, usually all you need to do is invoke your callback method (e.g., clipUpdate).

Dispatchable Event Library Details

The Dispatchable Event Library contains several useful helper classes able to dispatch any kind of event. The key classes are found in the package org.recoil.pixel.dispatchable:

DispatchableEventDispatcher

Fires events using an Executor, but does not provide listener management. Useful when you want to add flexibility to existing event dispatch code.

DispatchableEventSupport

Most applications will want to use this main helper class, which extends DispatchableEventDispatcher with listener management. It should be familiar if you know java.beans.PropertyChangeSupport.

PropertyChangeEventDispatcher

Combines PropertyChangeSupport with DispatchableEventDispatcher, providing flexible dispatch for PropertyChangeEvents. This is a good example to study to learn how to integrate DispatchableEvents with existing code.

DispatchableEvent

Abstract class that you extend with your event delivery code.

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

DirectExecutor calls the code provided to it synchronously in the same Thread that calls execute. If you use this class with DispatchableEventSupport you simply get the regular listener behavior, which makes it a useful default.

AWTExecutor

AWTExecutor schedules code on the AWT event dispatch thread. Events are interleaved with AWTEvents like MouseEvent and ActionEvent. Therefore, listeners called by this Executor are free to call methods that update AWT and Swing GUI components without using SwingUtilities.invokeLater(), because they are already called on the correct Thread.

MIDPExecutor

MIDPExecutor is the equivalent of AWTExecutor in the J2ME MIDlet world. It ensures that your events are delivered using the callSerially method 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
By Scott Oaks, Henry Wong

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

Andrew Thompson has been working with Java technology for nine years, and currently works for Finetix LLC.


Return to ONJava.com.

Copyright © 2009 O'Reilly Media, Inc.