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

发布于 2025-01-11 09:17:06 字数 755 浏览 0 评论 0原文

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

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

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

送君千里 2025-01-18 09:17:06

这是在 JLS,§17.6 中指定的。单词撕裂

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

因此,在您的示例中,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 的任何线程都将看到一致的字段值,无论其余应用程序中发生什么情况。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文