|
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.
|
Enviado por codecraig en enero 09, 2006 a las 02:06 PM CET #
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 05:19 PM CET #
Enviado por Vaidya en enero 10, 2006 a las 05:19 PM CET #
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 06:15 PM CET #
Hi all, well, thanks for your comments. The fact is that all of you are right:
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 11:39 PM CET #
Enviado por Paul Rivers en enero 12, 2006 a las 04:41 PM CET #
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 10:17 PM CET #
Enviado por Buminda en enero 14, 2006 a las 05:32 AM CET #
We're using the submit(Callable) argument instead (SwingWorker is Callable too).
Cheers,
Antonio
Enviado por Antonio en enero 14, 2006 a las 10:05 AM CET #
Enviado por Paul Rivers en enero 24, 2006 a las 10:47 PM CET #
Enviado por 125.16.133.194 en febrero 17, 2006 a las 12:56 PM CET #