Home > Mobile >  Is mutating two distinct part of an object in an unsynchronized way unsafe?
Is mutating two distinct part of an object in an unsynchronized way unsafe?

Time:03-03

Lets say I have a relatively simple object with two properties :

@Data
public class MyObject {
    public Integer a;
    public Integer b;
}

can I safely mutate a in some thread and b in some other thread safely ? for example, would this code be safe from race conditions ?

public MyObject compute() {
    MyObject newObj = new MyObject();
    List<Runnable> tasks = new ArrayList<>();
    Runnable computeATask = () -> {
        Integer a = computeA();
        newObj.setA(a);
    };
    Runnable computeBTask = () -> {
        Integer b = computeB();
        newObj.setB(b);
    };
    tasks.add(computeATask);
    tasks.add(computeBTask);
    tasks.stream().parallel().forEach(Runnable::run);
    return newObj;
}

CodePudding user response:

This is specified in JLS, §17.6. Word Tearing:

One consideration for implementations of the Java Virtual Machine is that every field and array element is considered distinct; updates to one field or element must not interact with reads or updates of any other field or element.

So the fact that a might written by a different thread than b in your example, does not create any data race.

But it still requires a thread safe mechanism to read the result. In your example, it’s the parallel stream which guarantees that the initiating thread can safely read the two variables after forEach returned.

You example can be simplified to

public MyObject compute() {
    MyObject newObj = new MyObject();
    Stream.<Runnable>of(() -> newObj.setA(computeA()), () -> newObj.setB(computeB()))
        .parallel().forEach(Runnable::run);
    return newObj;
}

But the recommended pattern would be to execute the calculation first, followed by constructing the object, which can be designed as immutable object then.

public class MyObject {
  public final Integer a, b;

  public MyObject(Integer a, Integer b) {
      this.a = a;
      this.b = b;
  }
}
public MyObject compute() {
    return CompletableFuture.supplyAsync(() -> computeA())
      .thenCombine(CompletableFuture.supplyAsync(() -> computeB()), MyObject::new)
      .join();
}

This way, you can be sure that any thread seeing the MyObject will see consistent values for the fields, regardless of what happens in the remaining application.

  • Related