ThreadPool
A
Thread Pool (TP) maintains a Blocking Queue of tasks and an array of
Threads to run them. Threads from the array keep on dequeing the BQ
(which blocks if its empty) and execute tasks if it has any.
The
BQ blocks if its full (and can optionally reject too).
That’s
why Thread-Pools take 2 arguments : Max no of threads to run and Max
size of BQ
ThreadPoolExecutors
ExecutorService
and ScheduledExecutorService
are interfaces to thread-pools and the classes implementing them are
ThreadPoolExecutor
and ScheduledThreadPoolExecutor
ThreadPoolExecutor
takes a Runnable or Callable and returns a Future.
The
executor then executes the FutureTask at some point in the future
whose result can be obtained by calling get() in the future object
(Note: If Runnable was submitted, then null is returned).
Future.get() call is blocking and returns only when that task is
complete.
Advantage
of ThreadPools is that they maintain a fixed number of threads in the
pool which do not die after doing one task but switch on to the other
tasks after finishing. Hence, the overhead of creating new Threads
for each task is avoided and this improves performance.
To
improve performance, one should have one ThreadPoolExecutor if
possible. Having 2 thread-pool-executors in the system may not be
good because if one of the pools’ blocking queue is empty, then
its threads will just lie waiting while the other pool’s
threads may be overworked at that very instant. If there were just
one pool, then all the threads would be working together to give the
maximum performance.
ScheduledThreadPoolExecutor
This
executor optionally takes parameters to delay the execution of a
task.
It
also has parameters to:
Run
task repeatedly at every fixed interval
Run
task repeatedly with fixed delay between each run of the task.
If
2 tasks’ schedule matures at the same time, then they are run
in FIFO fashion.
ForkJoinPool
This
is another implementation of ExecutorService whose worker threads
have the
property
of work-stealing
i.e. if a worker thread is idle, it will attempt to share sub-task
from a running thread.
Example
usage:
ForkJoinPool
forkJoinPool = new ForkJoinPool ();
HeavyWork
masterTask = new HeavyWork (0, end); // where end is a big
number.
forkJoinPool.invoke
(masterTask);
The
HeavyWork is defined below.
It
see if the pool has any idle worker threads and distributes sub-tasks
among them if any thread is available. If not, it proceeds with
calculation of the task itself.
Difference
between Callable and Runnable
Both
interfaces are meant to be used for multi-threading purposes.
Runnable
is the older interface while Callable was introduced in Java 5.
Callable
returns a result and can throw a checked exception but Runnable
cannot do both of these.
Runnable
mandates implementation of void run ()
Callable
mandates implementation of T call () throws Exception
Thread
class cannot work with Callable objects.
Callable
objects have to be submitted to an executor which returns a Future
object on submission. Future has methods to retrieve the result of
call() after execution.
Note:
The fact that Callable can return a result is interesting as it’s
not just a change in the method signature. When multiple threads are
launched to complete a big task, the parent thread might not wait at
the same place i.e. control of execution in parent thread might move
ahead. In such a case, where would the call() method return its
results and where would it throw an exception?
It
turns out that the executor service maintains the result and
exception thrown per thread.
When
Future.get() is called, it checks if the corresponding thread has
completed. If yes, then it returns the result (or the exception),
else it blocks till called thread completes.
|