Search code examples
javamultithreadingcompletable-futurespring-async

Are CompletableFutures thread safe?


I have a thread that invokes two separate threads. It passes in the same CompletableFuture to both of those child threads. If .get() was called in both of those threads at the exact same time, would I get any type of concurrency issues?

  • could it corrupt the CompletableFuture?
  • is it possible that I don’t see the last changes done on the object returned by .get()?
  • what if I modify that object afterwards?

As a concrete example, in the following code, would it be possible that the two threads print a different value, assuming nothing changes the object returned by cfInput.get() after cfInput is completed?

public void mainClass(CompletableFuture<ObjA> cfInput){
  class1.doAsync1(cfInput);
  class2.doAsync2(cfInput);
}

@Async
public void doAsync1(CompletableFuture<ObjA> cfInput){
  //logic
  System.out.println(cfInput.get().getObjB().getBlah());
  //logic
}

@Async
public void doAsync2(CompletableFuture<ObjA> cfInput){
  //logic
  System.out.println(cfInput.get().getObjB().getBlah());
  //logic
}

public class ObjA{
  private ObjB objB;
  public ObjB getObjB();
  public void setObjB();
}
public class ObjB{
  private String blah;
  public String getBlah();
  public void setBlah();
}

Solution

  • CompletableFuture is inherently thread-safe

    You could simply assume this from the fact that this class is designed to be used in a multi-threaded context, however this is more clearly specified in the description of the java.util.concurrent package:

    Memory Consistency Properties

    Chapter 17 of the Java Language Specification defines the happens-before relation on memory operations such as reads and writes of shared variables. The results of a write by one thread are guaranteed to be visible to a read by another thread only if the write operation happens-before the read operation. […] The methods of all classes in java.util.concurrent and its subpackages extend these guarantees to higher-level synchronization. In particular:

    • […]
    • Actions taken by the asynchronous computation represented by a Future happen-before actions subsequent to the retrieval of the result via Future.get() in another thread.

    So this implies that any write that is performed by a thread before it completes a future will be visible to any other thread that calls get() on that future (i.e. it “happened-before”).

    Your objects are not inherently thread-safe

    … nor are they “protected” by CompletableFuture

    Even though CompletableFuture itself is thread-safe, and provides some guarantees on the visibility of your writes, it does not make your objects thread-safe.

    For instance, if you modify the object returned by CompletableFuture.get(), those changes are not guaranteed to be visible to any other thread until you enter another happens-before relationship. You may thus need additional synchronization to enforce thread-safety on that object.