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
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
new SwingWorker
// This method executes on the background worker thread
@Override
protected
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
workerThreadPool.submit(worker);
}
}
Very nice idea. In fact I find it quite odd that the default SwingWorker stuff provides no pool management stuff. With a ThreadPoolExecutor you have a plethora of useful tools, getTaskCount, getQueue, purge... if you need them. Makes it much easier to display all the background tasks as JProgressBars in one grouped JDialog, for example. Nice one.
ReplyDeleteIn fact... are you by any chance prepared to make known your full implementation of this concept, not just the "simple implementation" above? No doubt you will have dealt with many challenges and thought up a few nice features...
ReplyDelete