以不同步的方式改变对象的两个不同部分是否不安全?
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
的任何线程都将看到一致的字段值,而不管其余应用程序中发生了什么。
假设我有一个具有两个属性的相对简单的对象:
@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
的任何线程都将看到一致的字段值,而不管其余应用程序中发生了什么。