This article was originally posted on October 5th, 2011 on JRoller.com.
One practical class when developing Swing application is the SwingWorker. It allows you to start a lengthy task in the background, and then applying the result to the display on the Event Thread. The usual pattern is the following:
class MySwingWorker extends SwingWorker {
@Override
public Void doInBackground() {
//perform background work
}
@Override
protected void done() {
//update display
}
}
While the background work might be long, it is possible also to update the user interface with partial results. In which case, the pattern is the following:
class MySwingWorker extends SwingWorker<Void, Integer> {
@Override
public Void doInBackground() {
while (!finished) {
//perform background work
publish(partialResult)
}
return result;
}
@Override
protected void process(List<Integer> chunks) {
//update display with partial results
}
}
This seemed so practical for me, that I decided to use this instead of one of our Threads that is running in the background, waiting for some data, processing it, then adding one or several lines to a JTable. The idea would be to have my SwingWorker running constantly in the background, waiting for data from a blocking queue, then publishing the result to the Swing updating part. It looked like this:
class MyEternalSwingWorker extends SwingWorker {
@Override
public Void doInBackground() {
while (!isCancelled()) {
queue.take();
//perform background work
publish(row)
}
return null;
}
@Override
protected void process(List chunks) {
//add rows to the JTable
}
}
If data were to arrive too fast, Swing has the possibility to coalesce all the updates, which should bring better performance. It worked quite fine, but suddenly, some of the users started to have some strange behaviors, and updates stopped quite unexpectedly for some windows. As it appears, it depends on the number of windows opened, and so on the number of such SwingWorkers running. That is when I discovered this interesting line in the SwingWorker code:
private static final int MAX_WORKER_THREADS = 10;
SwingWorker is using a Thread Pool, with 10 Threads, and there is no way to change that. So all my nice Eternal Swing Workers were just using all the available Threads in the Pool, and no other Worker could start their background work.
So beware, SwingWorkers can not be used as Threads. They need to be used for finite work. Or be less than 10.
Update: As Eugene Ho suggested in the comments, SwingWorker is actually a Runnable. So you can run it in your own Thread without blocking anything in the SwingWorker's ThreadPool, while keeping all the advantages, such as events coalescing, of the class.
Update 2: Also, the number of Threads in the pool is a constant, but it is possible to override it with your own Thread Pool, since SwingWorker use the sun.awt.AppContext class to fetch it. Do the following:
sun.awt.AppContext.getAppContext().put(SwingWorker.class, myThreadPoolExecutor);
Seeing the updates on my article, I can see that it is a pity that I could not retrieve the original comments, like the one from Eugene Ho. However, I checked the SwingWorker code, and all this is still true.