Monday, January 11, 2010

Throttling the SwingWorker using an ExecutorService

The SwingWorker is a utility class that ships with Java 6. It allows a Swing application to perform lengthy background computation in a separate worker thread, in effect freeing the event dispatch thread to interact with the user. Even though the SwingWorker utility is an important addition to the Java SDK, it does increase the resource overhead of the application by creating two additional threads for processing the lengthy computation - one thread performs the actual background work, while the other waits for the background thread to finish and then updates the results on the UI. Since the event dispatch thread is free to accept user input, the user - in the absence of a prompt response - may invoke the same functionality repeatedly. This results in a large number of worker threads being instantiated, and for a J2EE application, this in turn results in an increase in the number of associated threads being spawned by the servlet container to process the client requests. The increased number of threads on the server-side typically results in server overload and performance degradation. Even though the SwingWorker utility provides a cancel() method to stop the execution of an existing worker thread, there is no way to cancel the execution of the server-side thread created by the servlet container. The solution to this problem is to throttle the SwingWorker utility by using the ExecutorService, which has been added in Java 5 to execute Runnables using a thread pool. A fixed sized thread pool ExecutorService allows only a certain number of SwingWorker threads to be active at anytime, with the new threads having to wait for the earlier ones to finish, before getting a chance to execute. The value of the thread pool size is specific to the application and is primarily dependent on how many SwingWorker threads are expected to be active at any given time.

The code sample given below depicts a typical Swing application that uses the SwingWorker utility to retrieve data from the server. The SwingWorker utility is parameterized to have any desired return type , which is returned from the doInBackground() method. The type is used to denote the intermediate results that are used by the publish() and process() methods to depict - if required - the progress to the user. The doInBackgound() method is executed by the background worker thread that performs the lengthy computation. A second thread blocks at the get() call in the done() method and the event dispatch thread continues to perform user interaction. Finally, once the lengthy background computation is complete, the get() method returns the result of the doInBackground() method, which is then used by the second waiting thread to update the results on the Swing UI.

As explained above, once a SwingWorker thread is submitted for execution, it may be subsequently cancelled by invoking the cancel() method on the SwingWorker instance created. However, it is not possible to cancel the server-side thread that is spawned by the servlet container to process the client request. To avoid this problem, it is advisable to throttle the number of threads being created by using an ExecutorService with a fix thread pool of a certain size. Therefore, instead of calling the execute() method on the SwingWorker instance, the SwingWorker instance - which is a Runnable - is submitted to an implementation of the ExecutorService.

// Create a background worker thread

SwingWorker swingWorker =

new SwingWorker, Void>() {

// This method executes on the background worker thread

@Override

protected doInBackground() throws Exception {

compute result;

return result;

}

// This method executes on the UI thread

@Override

protected void done() {

result = get();

}

};

// Submit to the executor

SwingWorkerExecutor.getInstance().execute(swingWorker);

Given below is a very simple implementation of the SwingWorkerExecutor that creates an ExecutorService with a fixed thread pool size set as 3, which allows only three worker threads to be active at any given time. New Runnable instances of SwingWorker wait in the queue and are selected for execution only when a previous instance has completed execution. This strategy effectively avoids the spawning of numerous threads of the server, and therefore, prevents any possible performance degradation.

public class SwingWorkerExecutor {

private static final int MAX_WORKER_THREAD = 3;

private static final SwingWorkerExecutor executor = new SwingWorkerExecutor();

// Thread pool for worker thread execution

private ExecutorService workerThreadPool = Executors.newFixedThreadPool(MAX_WORKER_THREAD);

/**

* Private constructor required for the singleton pattern.

*/

private SwingWorkerExecutor() {

}

/**

* Returns the singleton instance.

* @return SwingWorkerExecutor - Singleton.

*/

public static SwingWorkerExecutor getInstance() {

return executor;

}

/**

* Adds the SwingWorker to the thread pool for execution.

* @param worker - The SwingWorker thread to execute.

*/

public void execute(SwingWorker worker) {

workerThreadPool.submit(worker);

}

}