以不同步的方式改变对象的两个不同部分是否不安全?

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

假设我有一个具有两个属性的相对简单的对象:

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

我可以安全地改变某个线程中的 a 和其他线程中的 b 吗?例如,这段代码在竞争条件下是否安全?

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;
}

这是在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.

因此,在您的示例中,a 可能由与 b 不同的线程写入,这一事实不会造成任何数据竞争。

但是读取结果还是需要线程安全的机制。在您的示例中,是并行流保证启动线程可以在 forEach 返回后安全地读取两个变量。

你的例子可以简化为

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

但是推荐的模式是先执行计算,再构造对象,然后可以设计成不可变对象。

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();
}

这样,您可以确保看到 MyObject 的任何线程都将看到一致的字段值,而不管其余应用程序中发生了什么。