Parallel task execution in J2EE using the Work Manager specification

by Dmitri Maximovich
05/16/2005

Abstract

Until recently, there has been no easy way to initiate the execution of parallel tasks in J2EE applications. Imagine your application needs to handle client requests (either in a servlet or session bean) that could be executed more efficiently in parallel. As a trivial example, think about client requests that can be processed by requesting data from more than one URL, web service, or SQL select statement where the order of execution is not important. These use cases are screaming to be executed in parallel; they spend most of the time waiting for responses and therefore don't consume much processing power. Besides, as many have already realized, solid concurrent design is a key component of scalable software.

This article takes a look at parallel task execution in J2EE through the use of a specification called Work Manager. We create a working example of how to use the Work Manager specification to achieve the execution of tasks in parallel in the servlet container.

Concurrency in J2EE

What options are available for achieving parallel tasks execution in the J2EE container? Amazingly, not many. As you are probably aware, the J2EE specification prohibits the creation of threads in managed code running in a J2EE container. (This is rarely enforced in practice, and some components are known to break this rule, for example the Quartz scheduler.) However, the J2EE specification does not discuss parallel execution at all, so developers needing to implement it are on their own.

In practice, only one option is generally available if you want to stay compatible with the J2EE specification: Use queue(s) to break synchronous calls into multiple asynchronous tasks. This approach works but is overly complex in implementation; it requires you to define destinations, create messages, and code MDBs. Moreover, you have to use persistent queue(s) if you can't afford to lose messages that could potentially require access to another resource manager. (An example is if your code runs in WebLogic but should use IBM MQ for queuing. From here it's one step to XA transactions.)

Another difficulty arises if you need to wait until a set of asynchronous child jobs are completed before continuing the main execution flow. As you will soon see, this is easily accomplished using the new Work Manager framework.

The Work Manager for Application Servers Specification

It looks like all these complexities could soon be something of the past as two major players in the J2EE server market, IBM and BEA, are collaborating on a specification that provides a simple, container-manageable programming model for the concurrent execution of work. This initiative is known as CommonJ, and a component of this is the Work Manager for Application Servers specification, now available as JSR 237. The specification was first published in 2003 and IBM had support for parallel tasks execution in WebSphere Application Server, starting from version 5 Enterprise Edition as part of Programming Model Extensions (PME). In WebSphere documentation, this functionality is sometimes referred to as "asynchronous beans." In WebSphere 6.0 it will be available in all editions. BEA WebLogic Server now offers a comparable feature in BEA WebLogic Server 9.0. With support from both BEA and IBM, there is a good chance that the specification will become at least a de facto standard in the near future.

Using the Work Manager Functionality

Let's take a look at what it takes to start using this new functionality. The following example was tested on WebLogic Server 9.0 beta. For simplicity we will use a servlet as the execution starting point in this example, but the same logic is applicable if your application entry point is a session bean or message driven bean.

Assume an imaginative use case where a servlet receives a list of words that have to be translated from one language to another (for example, by using Google's language tools or some similar remote service). The words could originate from a multiple selection list in an HTML page. The servlet code has to translate all words before returning a result to the client. We won't implement the code that performs the actual translation for this exercise; instead, we'll use a dummy translator with configurable delay to simulate a remote call to a translation service.

Translator interface and implementation

Let's define the interface for the Translator:

public interface Translator {

    public String getSource();

    public void translate();

    public String getTranslation();

}

As you can see, the Translator is designed to be stateful. Assume that your code must create instances of Translator, passing words to translate as an argument in a constructor call, and then invoke the translate() method, and finally retrieve the translated word by calling the getTranslation(). Arguably it's not the best design for such a task, and probably a single String translate(String) method would have been enough in our case, but as you will shortly see this will allow us to blend nicely with the Work interface when we execute translations in parallel. For the purposes of our example we're going to use a very simple DummyTranslator implementation. But first, let's define AbstractTranslator to encapsulate fields and methods that are going to be common for any implementation:

public abstract class AbstractTranslator implements Translator {

    protected final String source;

    protected String translation;



    public AbstractTranslator(String source) {

        this.source = source;

    }



    public String getSource() {

        return this.source;

    }



    public String getTranslation() {

        return this.translation;

    }



    public String toString() {

        return "source="+getSource()+", translation="+getTranslation();

    }

}

Now any concrete implementation needs to implement only the translate() method:

public class DummyTranslator extends AbstractTranslator {

    private final long delay;



    public DummyTranslator(String source, long delay) {

        super(source);

        this.delay = delay;

    }



    public void translate() {

        // delay to simulate network call

        try {

            Thread.sleep(this.delay);

        } 

        catch (InterruptedException ignore) { }

        this.translation = this.source+"_tr";

    }

}

We're ready to implement our servlet, first with traditional, sequential execution, and later with parallel task execution.

Pages: 1, 2, 3, 4

Next Page ยป