Class Experiment90
Experiment Suite 02
This suite of experiments aims to get some basic practice with Future
results that are based on
Runnable
and Callable
tasks, so that as we explore further we have a good sense
of how these work. The generic term 'task' is used to represent some unit of computation, generally
a concurrent computation. In the previous Experiments I used the term 'item' to represent the identity
of a task, or more precisely, the identity of something needing the results of a task.
Note: aside from returning a result, a Callable can also throw an Exception, while a Runnable cannot throw a Checked Exception.
In these experiments, it is the responsibility of the ExecutorService
to spawn a task,
but it's the responsibility of a Future
to interact with that task after it has been spawned.
I use the term spawn from my time working with Akka, where Actors would 'spawn'
child actors, where the word 'spawn' implies a parent/child relationship. Akka supports Structure
Concurrency via Actors in a similar way ExecutorService can, it just looks a little different, and is a
little harder to manage. Note: An Akka Actor is a much higher level Concurrency Construct than an ExecutorService
tasks, but even at this level, Structured Concurrency is still important.
In Structured Concurrency, the parent/child relationship is very important, because it implies a hierarchical structure.
We may remember when we were a child, when our mom was angry, she might have said "I brought you into this world, and I can take you out." Dark humor aside, one aspect of Structured Concurrency is that: generally, the parent should be in control of its children, including monitoring them, and stopping them when the need arises, where a Future can do both.
ExecutorService.awaitTermination(long, TimeUnit)
- Rather than just invoke
ExecutorService.close()
which is implicit in the try-with-resources block, if we explicitly wait for all the children to complete, we can define a timeout for all of them collectively. It is good practice in Concurrent Programming to use timeouts to create more deterministic applications. ExecutorService.shutdown()
- If at some point the parent decides to abandon all further processing, it can stop accepting work, but let any working tasks complete.
ExecutorService.shutdownNow()
- A stronger form of shutdown(), this will try to cancel any tasks that are still running. For example, we might want to do this after calling awaitTermination(timeout) if we do hit the timout. It would be good practice to again call awaitTermination(timeout) and do something more necessary if we hit the timeout again. Another reason to call this is when we decided we already have enough results from workers, and don't need any more.
ExecutorService.invokeAny(Collection, long, TimeUnit)
- Similar to the above, if you don't need the result of all tasks, if we only need the results of one successful task, it is easy to create a collection of submittable tasks, and then just wait for the result of the first one to succeed. When one succeeds, the ExecutorService will do a shutdownNow(). Again, including a timeout is good practice.
-
ExecutorService.invokeAll(Collection, long, TimeUnit)
- Similar to the above, if we really want the results of all child tasks, this is the most concise way to ask for it. Unlike the above, the result is a Future result, or more specifically, a List of Future results, so some more thought needs to be put into it.
Future.get(long, TimeUnit)
- Rather than just invoke
Future.get()
, by specifying a timeout, we make our code more deterministic by limiting how long it is reasonable to wait for a result. Unlike the above collective methods, sometimes we want to get down to the individual task level, and so we deal with the individual Futures for each task. Future#join()
- While similar to get(), there is currently no join(long, TimeUnit) that will timeout. Without further details, I advise against it, unless there is some compelling advantage in using it.
Future.cancel(boolean)
- Sometimes there is good reason to cancel the execution of a task, and this is the way to do it. Note the parameter mayInterruptIfRunning, when true can provide a more certain cancellation if necessary. Note also, while we can cancel a Future, we cannot cancel a Thread, whether Platform or Virtual. While we can Interrupt a Thread, we cannot Interrupt a Future.
-
Constructor Summary
-
Method Summary
-
Constructor Details
-
Experiment90
public Experiment90()
-
-
Method Details
-
main
-