comments on Executor

Doug Lea dl@cs.oswego.edu
Sat, 2 Feb 2002 10:49:33 -0500


Mark Anderson wrote...

> A few comments on Executor based on my attempt to use dl's concurrent package in
> a web application.

(Background for others: Mark had also written me about some related
issues in dl. util.concurrent. My reply mixes some details about
current versions of Executor and JSR-166 plans).

Answering these out of order...

> 2. More support for cancellation.

I agree that the more simple things we can do to simplify cancellation
the better.  Various forms of cancellation seem to be extremely
common, and common design problems, in multithreaded Java
applications. At least I get a lot of mail about it.

Some common issues are easy and worthwhile to deal with. As very briefly
mentioned in
  http://gee.cs.oswego.edu/dl/concurrency-interest/aims.html
it is worth supplying a simple Runnable class that maintains a
completion/cancellation status field. This is needed in a lot of
applications, so ought to be standardized.  For the sake of
concreteness, I just updated sketch of a trial version. 
Javadocs at:
  http://gee.cs.oswego.edu/dl/concurrent/index.html
Raw source at:
  http://gee.cs.oswego.edu/dl/concurrent/java/util/concurrent/
See class RunnableTask, as well as revised Executor and
AbstractExecutor. (RunnableTask could stand having a better name.
Suggestions welcome.) API design note: Since abstract class
RunnableTask implements Runnable, the method to execute one must have
a different name than just "execute" to avoid Java static dispatching
surprises.

Similar ideas and implementations apply to Futures, but for now
only the Runnable version is there.

Just this minimal support suffices in many cases -- if you want to
track completion or be able to cancel execution before it starts, you
would choose RunnableTask over Runnable.  But it cannot automatically
deal with asynchronous interruption.  Cancellation status can only be
checked automatically before execution, although programmers can also
put in their own code inside their own run() methods to check
isDone/isCancelled status.

Doing more than this requires tie-ins with Thread.interrupt mechanics
to deal with the problem that a cancelled RunnableTask can stay
blocked in a wait, since cancellation status cannot be linked
automatically to Thread.interrupt status. And interruption does not
now interact very nicely with some forms of Executor.  Executors allow
one thread to run many runnables. Possibly even the caller thread. So
you don't necessarily want to terminate the thread running the
Runnable. Yet most people write interruption handling code assuming
that the thread will terminate (mainly because all other courses of
action are at best poorly supported in Java).  This is one reason that
interrupted threads are just thrown away, replaced with fresh ones in
PooledExecutor.

In dl.util.concurrent, Joe Bowbeer and I evaded this in one specific
case by putting in a TimedCallable class that cancels upon timeout,
but only works with thread-per-call (i.e., each one is run in a fresh
thread), not arbitrary Executors. Again, this is because you don't
know in the general case which thread to interrupt upon cancellation,
and even if you did, you can only cleanly interrupt if you are sure it
is OK for that thread to terminate.

It would be possible to do something similar with Executors in general
(by recording which thread was running which runnable) only if we
required that all Executor implementations be written such that it is
OK to terminate any given thread running a runnable. This is not quite
true even for current PooledExecutor under the default policy of the
caller thread executing the runnable when pool is saturated. (BTW,
this works now because interruptAll only interrupts threads in the
pool.) Another possibility is to somehow make an Executor subinterface
covering implementations with this property.  Or maybe something
better.

In other words, I don't at the moment have a good plan for integrating
thread interruption and task cancellation. And for now, if you need
full asynch interruption support, I am afraid that you will be need to
create your own custom Executor class rather than than just plugging
in PooledExecutor. I guess my best recommendation is to first consider
whther you can live with the RunnableTask approach, which you COULD
use with simple extensions of PooledExecutor that includes an
executeTask method.  You might be able to just copy/paste/hack my
prototype code.

Consider this all as fodder for rethinking Thread.interrupt in general.

> 1. task groups vs. executor groups.

I think you are looking for a CompositeRunnableTask (as in the GoF
Composite pattern), where each of the elements can be run in parallel,
but are cancellable as a unit. It would not be hard to build this
on top of RunnableTask and a simple specialized Executor method. But
it still faces the task-cancellation vs thread-interruption issues above.

> 3. Accessors.
> The Executor has no exposed way to iterate over its current Runnables.

In PooledExecutor, the Runnables aren't necessarily all in one place
at any given time.  Some Runnables are sitting in queues (see method
drain()), while others are in the process of being dequeued and run.
PooledExecutor doesn't itself have a way to iterate over them. It
never needs to.  If applications need to track them, they have to
record them in separately. Considering that different applications
will want to specially track various Runnables for different
application-specific reasons anyway, I think this is the right
approach.

-Doug