I have a utility method (used for unit testing, it so happens) that executes a Runnable in another thread. It starts the thread running, but does not wait for the Thread to finish, instead relying on a Future. A caller of the method is expected to get() that Future. But is that enough to ensure safe publication of the computation done by the Runnable?
Here is the method:
private static Future<Void> runInOtherThread(final CountDownLatch ready, final Runnable operation) {
final CompletableFuture<Void> future = new CompletableFuture<Void>();
final Thread thread = new Thread(() -> {
try {
ready.await();
operation.run();
} catch (Throwable e) {
future.completeExceptionally(e);
return;
}
future.complete(null);
});
thread.start();
return future;
}
After calling Future.get() on the returned Future, can the caller of the method safely assume that the Runnable has finished execution, and its results have been safely published?
No you don't need to join(). Calling get() on the future is sufficient.
The CompletableFuture interface is a subtype of Future. And the javadoc for Future states this:
Memory consistency effects: Actions taken by the asynchronous computation happen-before actions following the corresponding
Future.get()in another thread.
That happen-before relationship is sufficient to ensure safe publication of the value returned by get().
Furthermore, the get() call will not complete until the CompletableFuture has been completed, exceptionally-completed or cancelled.
If we look at Safe Publication by Shipilev one of the trivial ways to get safe publication is to work:
Exchange the reference via a volatile field (JLS 17.4.5), or as the consequence of this rule, via the AtomicX classes
Since CompletableFuture uses a volatile field to write and read the value no additional memory barriers are necessary for safe publication. This is explained in CompletableFuture class overview comment:
* A CompletableFuture may have dependent completion actions,
* collected in a linked stack. It atomically completes by CASing
* a result field, and then pops off and runs those actions. This
* applies across normal vs exceptional outcomes, sync vs async
* actions, binary triggers, and various forms of completions.
*
* Non-nullness of volatile field "result" indicates done. It may
* be set directly if known to be thread-confined, else via CAS.
* An AltResult is used to box null as a result, as well as to
* hold exceptions.
It also handles the safe initialization of the published objects, as per the same overview comment later:
* Completion fields need not be declared as final or volatile
* because they are only visible to other threads upon safe
* publication.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With