Class Experiment01_Overview
Project Loom Overview Experiment
This experiment expands on the previous one, producing the same results, but doing so with a lot more code.
While this is still a very simple experiment, we will look at many of the new Project Loom bells and whistles, using some new best new practices, so that we are well grounded for further experiments. Feel free to ignore most of this for now, but remember it's here to get grounded again.
Virtual Threads
In simple terms, Virtual Threads are a new implementation of the classic Java Thread APIs, but critically, a more efficient implementation. Unlike Kotlin Coroutines, there are no language changes to support Project Loom. However, unlike Kotlin Coroutines, Virtual Threads are supported by new functionality in the JVM. Once Virtual Threads become stable and available, we should likely see a refactoring of many concurrency frameworks to use Virtual Threads, such as Kotlin Coroutines, Scala, Akka, Reactor and other Reactive frameworks.
Structured Concurrency
In addition to Virtual Threads, one of the important new features Project-Loom brings to JDK-18 is Structured Concurrency In a nutshell, Structured Concurrency is like eliminating goto in old style programming languages, creating better discipline in how we fork and join, keeping all such concurrency in a hierarchy of parents and children, or sessions and sub-sessions.
When the flow of execution splits into multiple concurrent flows, they rejoin in the same code block.
StructuredExecutor
PREVIEW is the heart of Structured Concurrency which is designed to work with
try-with-resources blocks, where the ExecutorService
resource is closed at the
end of the block. StructuredExecutor.open(String)
PREVIEW opens a 'Session' which implies a lifecycle
and lifetime. The lifecycle is best described as
- Get an ExecutorService instance and open a session, which starts the lifecycle of that session.
-
Define a Completion Handler from one of
- StructuredExecutor.ShutdownOnFailure() when you want to shutdown on any failure.
- StructuredExecutor.ShutdownOnSuccess() when you only care about the successfull results of one Task.
- StructuredExecutor.CompletionHandler for a custom Completion Handler.
-
Use
StructuredExecutor.fork(Callable, BiConsumer)
PREVIEW to fork (spawn) tasks according to the ThreadFactory inStructuredExecutor.open(String,ThreadFactory)
PREVIEW. If not specified, the default is Virtual Threads.BiConsumer
the Completion Handler. -
Optionally, call
StructuredExecutor.shutdown()
to cancel all uncompleted Tasks. When this method returns, the calling join() will return immediately without blocking/waiting. We may also call this afterStructuredExecutor.joinUntil(Instant)
as in the code below, where our policy is to shutdown after timeout. Note: this is very different thanExecutorService.shutdown()
so there is a bit of paradigm shift here. -
Always call
StructuredExecutor.join()
to wait for the lifecycles of all the spawned tasks to complete. Note, if these child Tasks spawn their own Tasks, those lifecycles must also complete first. - Handle Execution Failures, such as with StructuredExecutor.ShutdownOnFailure.throwIfFailed(); Basically, we need to deal with this before the try-with-resources block implicitly calls close() in the finally stage, because try-with-resources is not flexible enough to handle this situation.
- Optionally, collect the results of all Tasks. If there are failures of some, but not all Tasks, handling this is also shown below.
- Close the StructuredExecutor resource implicitly, finally completing its lifetime.
Conclusions
The StructuredExecutor we create here is a 'child' node of the Thread running, inheriting the ScopeLocal
PREVIEW
values of the thread, and each child that is forked becomes a child node of the StructuredExecutor, also
inheriting the ScopeLocal
PREVIEW values. In this way, we can manage all forked threads as a group in a
well-disciplined way. In a sense, the StructuredExecutor and the Thread it is opened from, are the parents of
the sibling tasks that are spawned. In short, Project Loom is an attempt to make Concurrent Families less
dysfunctional... 😉
Streams and Lazy Evaluation
One trap I stumbled into with this experiement, was forgetting to terminate the Stream
I was
using to spawn the Tasks. My original code looked like
var completedResults = futureStream.map(Future::resultNow).toList();Which threw an IllegalStateException because the tasks were not spawned until toList() is called. The problem was not obvious to me, and I had to ask help from the Loom Developers to understand it. So, when spawning tasks via a Stream, always remember to terminate the stream before calling join().
-
Constructor Summary
-
Method Summary
-
Constructor Details
-
Experiment01_Overview
public Experiment01_Overview()
-
-
Method Details
-
main
-