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