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

Jou*_*las 2 java multithreading synchronized

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

@Data
public class MyObject {
    public Integer a;
    public Integer b;
}
Run Code Online (Sandbox Code Playgroud)

我可以安全地在某个线程中改变 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;
}
Run Code Online (Sandbox Code Playgroud)

Hol*_*ger 5

这是在JLS\xc2\xa717.6 中指定的。单词撕裂

\n
\n

Java 虚拟机实现的一个考虑因素是每个字段和数组元素都被认为是不同的;对一个字段或元素的更新不得与任何其他字段或元素的读取或更新交互。

\n
\n

a因此,可能由与示例中不同的线程编写的事实b不会造成任何数据争用。

\n

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

\n

你的例子可以简化为

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

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

\n
public class MyObject {\n  public final Integer a, b;\n\n  public MyObject(Integer a, Integer b) {\n      this.a = a;\n      this.b = b;\n  }\n}\n
Run Code Online (Sandbox Code Playgroud)\n
public MyObject compute() {\n    return CompletableFuture.supplyAsync(() -> computeA())\n      .thenCombine(CompletableFuture.supplyAsync(() -> computeB()), MyObject::new)\n      .join();\n}\n
Run Code Online (Sandbox Code Playgroud)\n

这样,您可以确保任何看到该字段的线程都MyObject将看到一致的字段值,无论其余应用程序中发生什么情况。

\n