The core concept of "structured concurrency" is that when control splits into concurrent tasks that they join up again. If a main task splits into several concurrent sub-tasks to be executed by spawning threads then those threads must terminate before the main task can complete. The benefit is abstraction. The caller of a method that is invoked to do a task should not care if the method decomposes the work into sub-tasks that are executed by a million threads. When the method completes then all threads scheduled by the method should have terminated.
An early of prototype of Project Loom had API named FiberScope to support the scheduling of fibers (a precursor to virtual threads) with initial support in this area. There is no explicit support in the current prototype but it is possible to use existing constructs without needing too many new APIs. In particular, ExecutorService has been retrofitted to extend AutoCloseable so that it’s possible to write code like this:
Thread factory = Thread.builder().virtual().factory()try (ExecutorService executor = Executors.newUnboundedExecutor(factory)) {executor.schedule(task1);executor.schedule(task2);}
A thread executing this code will block in the executor’s close method until the two tasks have have completed and executor has terminated.
This structure lends itself to nesting or even a tree of tasks:
try (ExecutorService executor = Executors.newUnboundedExecutor(factory)) {
executor.submit(() -> foo());
executor.submit(() -> bar());
}void foo() {try (ExecutorService executor = Executors.newUnboundedExecutor(factory)) {executor.submit(...);
}}void bar() {try (ExecutorService executor = Executors.newUnboundedExecutor(factory)) {executor.submit(...);}}
In this example, the main task will not complete until foo and bar complete. The foo method, executing in a virtual thread, does not complete until the task that is submitted completes. Similarly bar, executing in another virtual thread, does not complete until the task that it submitted completes.
Cancellation and Deadlines
The early prototype of Project Loom has basic support for cancellation. Further exploration is needed but as virtual threads represented by java.lang.Thread objects it means that they support thread interruption. In addition, if a thread blocked in ExecutorService::close is interrupted then it will attempt to stop all tasks/threads as if by invoking the exector's shutdownNow method. If all tasks are well behaves and terminate quickly when interrupted then it allow the executor to terminate quickly.
An ExecutorService can be wrapped with a deadline so that its shutdowNow method is invoked if the deadline expires before the executor has terminated.
Thread factory = Thread.builder().virtual().factory()var deadline = Instant.now().plusSeconds(10);try (ExecutorService executor = Executors.newUnboundedExector(factory).withDeadline(deadline)) {executor.schedule(task1);executor.schedule(task2);}
Deadlines do not correctly work with nested usages. In the original FiberScope prototype, the scope had a owner that would be interrupted when the deadline expired. We may explored something similar here.