I’m working with CompletableFuture in Java and want to understand how field updates made inside a CompletableFuture task are visible to the main thread after calling join(). Specifically, if I pass an object (x) to a CompletableFuture task, and the task updates a field inside that object (e.g., x.y), will the main thread always see the updated value after join()?
class Y {
private int value;
private List<String> names;
public Y() {
this.names = new ArrayList<>();
}
public void setValue(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public void addName(String name) {
this.names.add(name);
}
public List<String> getNames() {
return names;
}
}
class X {
private Y y;
public void setY(Y y) {
this.y = y;
}
public Y getY() {
return y;
}
}
public class Main {
public static void main(String[] args) {
X x = new X();
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
Y newY = new Y();
newY.setValue(42);
newY.addName("Alice");
newY.addName("Bob");
x.setY(newY); // Update the y field of x
});
future.join(); // Wait for the CompletableFuture to complete
// Will the main thread always see the updated y field?
System.out.println("Value: " + x.getY().getValue()); // Should print 42
System.out.println("Names: " + x.getY().getNames()); // Should print [Alice, Bob]
}
}
Are the updated fields (value and names) of x.y always guaranteed to be visible to the main thread after join()? Or I have to use AtomicInteger
, volatile
or Collections.synchronizedList
to achive that?
In my humble opinion the join()
method ensures that all actions performed by the CompletableFuture
task are completed before the main thread proceeds. This means that any updates to fields or objects made by the CompletableFuture
task will be visible to the main thread after join()
returns. But I'm not sure about memory visibility of these fields. They might not be visible always.
Note: This example is a simplified reflection of a more complex scenario. In practice, I understand that I could directly set the x object after CompletableFuture completes (e.g., x = future.join()). However, my actual use case involves nested objects and shared state across multiple threads, which is why I’m focusing on field visibility and thread safety.
I’m working with CompletableFuture in Java and want to understand how field updates made inside a CompletableFuture task are visible to the main thread after calling join(). Specifically, if I pass an object (x) to a CompletableFuture task, and the task updates a field inside that object (e.g., x.y), will the main thread always see the updated value after join()?
class Y {
private int value;
private List<String> names;
public Y() {
this.names = new ArrayList<>();
}
public void setValue(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public void addName(String name) {
this.names.add(name);
}
public List<String> getNames() {
return names;
}
}
class X {
private Y y;
public void setY(Y y) {
this.y = y;
}
public Y getY() {
return y;
}
}
public class Main {
public static void main(String[] args) {
X x = new X();
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
Y newY = new Y();
newY.setValue(42);
newY.addName("Alice");
newY.addName("Bob");
x.setY(newY); // Update the y field of x
});
future.join(); // Wait for the CompletableFuture to complete
// Will the main thread always see the updated y field?
System.out.println("Value: " + x.getY().getValue()); // Should print 42
System.out.println("Names: " + x.getY().getNames()); // Should print [Alice, Bob]
}
}
Are the updated fields (value and names) of x.y always guaranteed to be visible to the main thread after join()? Or I have to use AtomicInteger
, volatile
or Collections.synchronizedList
to achive that?
In my humble opinion the join()
method ensures that all actions performed by the CompletableFuture
task are completed before the main thread proceeds. This means that any updates to fields or objects made by the CompletableFuture
task will be visible to the main thread after join()
returns. But I'm not sure about memory visibility of these fields. They might not be visible always.
Note: This example is a simplified reflection of a more complex scenario. In practice, I understand that I could directly set the x object after CompletableFuture completes (e.g., x = future.join()). However, my actual use case involves nested objects and shared state across multiple threads, which is why I’m focusing on field visibility and thread safety.
Share Improve this question edited Feb 6 at 19:44 Kaepxer asked Feb 6 at 19:34 KaepxerKaepxer 4127 silver badges20 bronze badges 3- This might answer your question: stackoverflow.com/a/34444996/402428 – michid Commented Feb 7 at 11:56
- 1 The answer is about how changes in the main thread (before submitting a task) are visible to the worker thread but my question is the opposite. I want to know if changes made by the worker thread (inside CompletableFuture) are visible to the main thread. So I'm not sure answer could be same in opposite case actually. That's a great post though, thanks for sharing it! – Kaepxer Commented Feb 7 at 17:11
- 1 This comment may be relevant. – Slaw Commented Feb 7 at 20:25
1 Answer
Reset to default 4Docs of java.util.concurrent define happen-before relation:
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.
So if actions, that are made by the CompletableFuture
task, happen-before return from join
, then the field (x.y
) updates are visible to your main thread.
I have not found in the doc that CompletableFuture.join
shares the same guarantees with Future.get
.
But there is a comment in https://bugs.openjdk.org/browse/JDK-8292365 with suggestion to document CompletableFuture
guarantees:
Actions taken by the asynchronous computation represented by a {@code Future} happen-before actions subsequent to the retrieval of the result via {@code Future.get()}, {@code Future.resultNow()} , or {@code Future.exceptionNow()}, in another thread.
Similar for the computation represented by a {@code CompletabeFuture} and retrieval of the result via {@code CompletabeFuture.getNow} or {@code CompletabeFuture.join}.
This inclines me to think that CompletableFuture.join
shares the same guarantees with Future.get
but it is not documented yet.
And it is stated in the doc that actions made by Future.get
happen-before actions after it is called:
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.