SwingWorker: Throttling and monitoring?

The problem

I have a lot of RSS feeds to read. And I want to read them in my Swing application without blocking the event dispatch thread.

So, of course, I want to use SwingWorkers to fetch and parse all RSS feeds.

My first approach would be to create a SwingWorker for each of the RSS feed URLS. The SwingWorker would then download the RSS feed from the given URL (using a java.net.HttpURLConnection, for instance) and then parse it.

So I instantiate 200 SwingWorkers, each one holding a different URL, and "execute()" them. Cool.

But... wait a moment. 200 SwingWorkers? That's 200 threads, right?

Well, yes. That's 200 threads. And that's a problem too!

So, to summarize:

If you have a big number of tasks to execute asynchronously don't use a thread for each one.

Most operating systems won't complain directly if you create a big number of threads, but probably the system will be slow. There will be a lot of context switching between threads. And synchronization between them (if any) may be a problem.

Throttling

So in order to keep resource-consumption under control we would probably want to introduce some throttling. This is, we want to define a number M of simultaneous threads to execute those N tasks. And, of course, having M < N.

A pool of M threads seems a good idea to me. Building a pool of threads using Java 5 is a piece of cake:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
...
int M = 3;
ExecutorService threadPool = Executors.newFixedThreadPool( M );

And, well, executing SwingWorkers using that thread pool is also a piece of cake: we just need to invoke the "submit()" method on our Executor. Like this:

SwingWorker mySwingWorker = ...;
threadPool.submit( mySwingWorker );

Note that the SwingWorker can be cancelled as usual, this is, the SwingWorker semantics are not lost if you use your preferred ExecutorService. What I mean is that in order to cancel a SwingWorker you still can use the "cancel()" command as usual (and it's not necessary to handle the threadPool in any way). Like this:

mySwingWorker.cancel();

Having an ExecutorService is handy if you have a lot of things to do: you submit them all and that's all. The FixedThreadPool places all the pending tasks in an unbound queue. And then starts executing them in order. When one task finishes the thread is returned to the pool. If there are any pending tasks to be executed then the recently released thread starts executing one of those.

So, for instance, if we have N SwingWorkers to be executed stored in an ArrayList we can submit them all for execution with the following code:

ArrayList<SwingWorker> mySwingWorkers = ...
// This loops over all N workers to execute...
for( SwingWorker worker : mySwingWorkers )
  threadPool.submit( worker );

The Singleton Pattern

It may be interesting to have a reference to that ExecutorService handy in our application. We can achieve this by using the Singleton pattern. Something like this:

import ...;
public final class SwingWorkerExecutor
{
  /\*\* Number of threads in the pool. \*/
  private static final int M = 3;

  /\*\* The ExecutorService. \*/
  private static ExecutorService threadPool
    = Executors.newFixedThreadPool(M);

  /\*\*
   \* Schedules a SwingWorker for execution.
   \* @param aWorker a SwingWorker to execute.
   \*/
  public static <T,V> void execute( SwingWorker<T,V> aWorker )
  {
    threadPool.submit( aWorker );
  }
}

So we can say something like:

  SwingWorkerExecutor.submit( mySwingWorker );

What else? Monitoring?

We can also take advantage of this SingletonPattern in some other interesting ways. Monitoring, for instance. This is what I'm thinking of currently:

  • Make the SwingWorkerExecutor keep a ListModel with the list of the currently running SwingWorkers.
  • Or, even better, make the SwingWorkerExecutor keep a TableModel with the list of the currently running SwingWorkers, and their current progress.
  • Keep statistics inside the SwingWorkerExecutor about the average wait time (in the unbounded queue) and the average run time. This may be interesting to fine-tune an appropriate value for M (the number of threads) for different scenarios.
  • Build a mechanism for allowing time-out on execution of SwingWorkers

So I was wondering what you think. Do you see any other uses for such an hypothetical "SwingWorkerExecutor"? Any ideas or suggestions?

Cheers,
Antonio

UPDATE:

As Vaidya points out the SwingWorker implementation in JDK 6 has a pool of 10 threads, so it won't ever fire 200 threads.

Comentarios:

yes this could be quite useful, especially having the statistics available. But be sure not to bog down the general idea behind the SwingWorkerExecutor with potentially "cool" features. If interested perhaps a subclass of SwingWorkerExecutor could be created which adds the statistics to it, that way in development u use that and in production u use the original swingworkerexecutor w/o the stats. ...is this RSS reader u speak of realted to the EventBus? hope so :)

Enviado por codecraig en enero 09, 2006 a las 06:06 AM CET #

Would it be useful to modify it somehow to detect how many processors/MT execution units the system has and then schedule the number of possible threads to lock in for the pool? Or is that just a dumb idea? Reason being, as you pointed out a Nigara(spelling) has 32 hardware thread execution units, so having a few of those usable instead of the 3 thats currently there might make more sense? Was just a thought!

As for being useful, yea I think it might be. Have to think about it as I have a few upcoming projects that might like something like that.

Keep on rocking Antonio!

Enviado por Jeffrey Olson en enero 09, 2006 a las 09:19 AM CET #

I see that the SwingWorker class has a static ExecutorService field with MAX_WORKER_THREADS = 10. So, does 200 SwingWorker instances really mean 200 Threads ? I had the notion that 10 of them will be executing with rest waiting in the queue.

Enviado por Vaidya en enero 10, 2006 a las 09:19 AM CET #

Very interesting article.

I am used to use a single thread pool to execute SwingWorker threads for user tasks, which need to be executed in the same sequence as the user initiated them. This allows for type ahead scenarios: e.g. a user can choose the "Apply to all" menu and then immediately after that the "Save" menu. The single thread pool ensures, that the document is only saved to disk, after the "Apply to all" task has finished.

For tasks for which the sequence is not relevant, I used to use a thread for each invocation. After reading this article. I think, I am going to use for these tasks a thread pool with N threads too.

Enviado por Werner Randelshofer en enero 10, 2006 a las 10:15 AM CET #

Hi all, well, thanks for your comments. The fact is that all of you are right:

  • codecraig - yes, this is for the RSS reader with an event bus, and yes, a hierarchy of inheritance or something similar may be the way to go.
  • Jeffrey - yes, the number of threads should depend on the number of processors (that'll be another future entry)
  • Vaida - yes, the SwingWorker implementation includes a pool of 10 threads (which is a real pity if you're using a Niagara processor). Having the pool internal to the SwingWorker is a good idea, but I prefer to use an external one: much better control on what is going on ;-)
  • Werner - yes, it's much better to use a ThreadPool instead of spawning your own threads (less resource consumption, faster thread invocation, much more control for debugging purposes).

I think those things you've pointed out are not clear. Maybe I'll work them out in a future entry. Thanks for your input.

Cheers,
Antonio

Enviado por Antonio en enero 10, 2006 a las 03:39 PM CET #

SwingWorker has a pool of 10 threads? Ouch...that's going to be baaaad news. What if some of the threads get hung up? What if (and I bet you this will happen), there are 10 different swing workers that each open a modal dialog in the construct() method, which of course blocks until it returns. Suddenly, your app will stop working. :-(

Enviado por Paul Rivers en enero 12, 2006 a las 08:41 AM CET #

Hi Paul,

Which "construct" method?

Anyway if some of the threads gets hung up then you're in trouble: you've got a bug, right? ;-)

The situation is similar to a J2EE application that does not return database connections to a datasource. That datasource will exhaust all its connections and your application will stop working too.

Anyway if the problem is a real concern to you then you probably want to build your own ThreadFactory; and spawn a monitor thread that periodically checks how long your other threads have been running (and interrupts them in case of deadlock). That's probably a good idea for future enhancements.

Cheers,

Antonio

Enviado por Antonio en enero 12, 2006 a las 02:17 PM CET #

The ExecutorService threadPool.submit() method has Runnable as a paramenter , its not a swingworker.Can someone pls explain this.

Enviado por Buminda en enero 13, 2006 a las 09:32 PM CET #

Hi Buminda,
We're using the submit(Callable) argument instead (SwingWorker is Callable too).
Cheers,
Antonio

Enviado por Antonio en enero 14, 2006 a las 02:05 AM CET #

Antonio, Of course it's the construct method in the SwingWorker. If a SwingWorker is launched that opens a modal dialog DialogA, and DialogA launches a SwingWorker which launches a modal dialog DialogB, and DialogB launches a SwingWorker which launches a model dialog DialogC, and DialogC...and so and until you have 10 modal dialogs, eventually you'll try to launch another SwingWorker and nothing will happen. You might think that you shouldn't have 10 modal dialogs on top of each other but what if your application needs several JFrame's open, and each of those opens its own modal dialog - it's certainly possible that at some time you'll have 10 different modal dialogs open, and when you go to open the 11th...suddenly your application stops working. In this case, the threads aren't getting hung up because of a bug, they are locking as part of the desired behavior of JDialog. Method calls that block are often the kind of thing that is run on a SwingWorker. The situation \*is\* similar to a J2EE application that does not return database connections to a datasource. This is a major problem in web applications because if just one method that uses database connections forgets to close them, that one method can run the entire application out of connections. This is a problem - not one which I want to see creep into SwingWorker with no immediate benefit! Just to be clear, I think your idea of having a pool for RSS feeds is fantastic, and a very cool. I am only saying that by putting a thread pool limit of sorts in the jdk SwingWorker class is a terrible idea with the potential to lock up an application.

Enviado por Paul Rivers en enero 24, 2006 a las 02:47 PM CET #

AAAA

Enviado por guest en febrero 17, 2006 a las 04:56 AM CET #

Enviar un comentario:
Los comentarios han sido deshabilitados.
About

swinger

Search

Archives
« abril 2014
lunmarmiéjueviesábdom
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
    
       
Hoy