为什么 volatile 不能解决比较期间的数据竞争
Why volatile does not solve data race during comparison
我正在尝试使用多线程进行试验并遵循此处的示例:https://docs.oracle.com/javase/specs/jls/se10/html/jls-8.html#jls-8.3.1.4
我已经在下面发布了我的代码。
你能帮我理解为什么数据竞争发生在“if (x < y) {
”而不是“if (y > x) {
”吗?
我正在使用 openjdk-14.0.1:
Linux void-MS-7678 5.4.0-29-generic #33-Ubuntu SMP Wed Apr 29 14:32:27 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
代码:
public class Main {
public static void main(String[] args) {
DataRace dr = new DataRace();
Thread t1 = new Thread(()-> {
for (int i = 0; i < 100_000; i++) {
dr.increment();
}
});
Thread t2 = new Thread(()-> {
for (int i = 0; i < 100_000; i++) {
dr.check();
}
});
t1.start();
t2.start();
}
private static class DataRace {
private volatile int x = 0, y = 0;
public void increment() {
x++;
y++;
}
public void check() {
// System.out.println("x=" + x + " y="+ y); // - NO ISSUES
// if (y > x) { - NO ISSUES
// if (x < y) { - ISSUES
if (x < y) {
System.out.println("DataRace detected: x < y");
}
}
}
}
输出:
/home/void/.jdks/openjdk-14.0.1/bin/java -javaagent:/home/void/Development/idea-IC-183.4588.61/lib/idea_rt.jar=46411:/home/void/Development/idea-IC-183.4588.61/bin -Dfile.encoding=UTF-8 -classpath /home/void/Development/multithreading/out/production/classes Main
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
Process finished with exit code 0
比较 if (x < y) {
不是原子的。
t2
加载 x
进行比较
t2
停止工作
t1
递增 x
和 y
t1
停止
t2
开始
t2
加载 y
进行比较
- 因为
x
是旧值,y
是新值,递增,x < y
是 true
。
下面是一个如何用 synchronized
解决这个问题的例子:
class Main {
public static void main(String[] args) {
DataRace dr = new DataRace();
Thread t1 = new Thread(()-> {
for (int i = 0; i < 100_000; i++) {
dr.increment();
}
});
Thread t2 = new Thread(()-> {
for (int i = 0; i < 100_000; i++) {
dr.check();
}
});
t1.start();
t2.start();
}
private static class DataRace {
private volatile int x = 0, y = 0;
public synchronized void increment() {
x++;
y++;
}
public void check() {
// System.out.println("x=" + x + " y="+ y); // - NO ISSUES
// if (y > x) { - NO ISSUES
// if (x < y) { - ISSUES
boolean xSmallerY = false;
synchronized (this) {
xSmallerY = x < y;
}
if (xSmallerY) {
System.out.println("DataRace detected: x < y");
}
}
}
}
akuzminykh 已经 为什么 if (x < y)
可以是真的。您还问过为什么在 if (y > x)
.
时从未看到相同的现象
原因是在 java 中表达式总是从左到右求值,当你这样做 y > x
时,y
总是首先从内存中加载所以 x
在 y
之前已经递增,如果要从后续迭代中读取 x
,它也将大于 y
。
当您执行 y > x
时,您仍然可以看到 "DataRace detected" 正在打印,但当且仅当 x
接近 Integer.MAX_VALUE
并且它溢出并变为在从内存中读取 y
之后的后续迭代中为负,然后才从内存中读取 x
。
public class Check {
public static void main(String[] args) {
DataRace dr = new DataRace();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 100_000; i++) {
dr.increment();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 100_000; i++) {
dr.check();
}
});
t1.start();
t2.start();
}
private static class DataRace {
private volatile int x,y;
public void increment() {
// to make sure the race condition is caused by the ++ and not by the assignment
synchronized (this) {
x = Integer.MAX_VALUE;
y = Integer.MAX_VALUE;
}
x++;
y++;
}
public synchronized void check() {
if (y > x) {
System.out.println("DataRace detected: y > x");
}
}
}
}
我正在尝试使用多线程进行试验并遵循此处的示例:https://docs.oracle.com/javase/specs/jls/se10/html/jls-8.html#jls-8.3.1.4
我已经在下面发布了我的代码。
你能帮我理解为什么数据竞争发生在“if (x < y) {
”而不是“if (y > x) {
”吗?
我正在使用 openjdk-14.0.1:
Linux void-MS-7678 5.4.0-29-generic #33-Ubuntu SMP Wed Apr 29 14:32:27 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
代码:
public class Main {
public static void main(String[] args) {
DataRace dr = new DataRace();
Thread t1 = new Thread(()-> {
for (int i = 0; i < 100_000; i++) {
dr.increment();
}
});
Thread t2 = new Thread(()-> {
for (int i = 0; i < 100_000; i++) {
dr.check();
}
});
t1.start();
t2.start();
}
private static class DataRace {
private volatile int x = 0, y = 0;
public void increment() {
x++;
y++;
}
public void check() {
// System.out.println("x=" + x + " y="+ y); // - NO ISSUES
// if (y > x) { - NO ISSUES
// if (x < y) { - ISSUES
if (x < y) {
System.out.println("DataRace detected: x < y");
}
}
}
}
输出:
/home/void/.jdks/openjdk-14.0.1/bin/java -javaagent:/home/void/Development/idea-IC-183.4588.61/lib/idea_rt.jar=46411:/home/void/Development/idea-IC-183.4588.61/bin -Dfile.encoding=UTF-8 -classpath /home/void/Development/multithreading/out/production/classes Main
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
DataRace detected: x < y
Process finished with exit code 0
比较 if (x < y) {
不是原子的。
t2
加载x
进行比较t2
停止工作t1
递增x
和y
t1
停止t2
开始t2
加载y
进行比较- 因为
x
是旧值,y
是新值,递增,x < y
是true
。
下面是一个如何用 synchronized
解决这个问题的例子:
class Main {
public static void main(String[] args) {
DataRace dr = new DataRace();
Thread t1 = new Thread(()-> {
for (int i = 0; i < 100_000; i++) {
dr.increment();
}
});
Thread t2 = new Thread(()-> {
for (int i = 0; i < 100_000; i++) {
dr.check();
}
});
t1.start();
t2.start();
}
private static class DataRace {
private volatile int x = 0, y = 0;
public synchronized void increment() {
x++;
y++;
}
public void check() {
// System.out.println("x=" + x + " y="+ y); // - NO ISSUES
// if (y > x) { - NO ISSUES
// if (x < y) { - ISSUES
boolean xSmallerY = false;
synchronized (this) {
xSmallerY = x < y;
}
if (xSmallerY) {
System.out.println("DataRace detected: x < y");
}
}
}
}
akuzminykh 已经 if (x < y)
可以是真的。您还问过为什么在 if (y > x)
.
原因是在 java 中表达式总是从左到右求值,当你这样做 y > x
时,y
总是首先从内存中加载所以 x
在 y
之前已经递增,如果要从后续迭代中读取 x
,它也将大于 y
。
当您执行 y > x
时,您仍然可以看到 "DataRace detected" 正在打印,但当且仅当 x
接近 Integer.MAX_VALUE
并且它溢出并变为在从内存中读取 y
之后的后续迭代中为负,然后才从内存中读取 x
。
public class Check {
public static void main(String[] args) {
DataRace dr = new DataRace();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 100_000; i++) {
dr.increment();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 100_000; i++) {
dr.check();
}
});
t1.start();
t2.start();
}
private static class DataRace {
private volatile int x,y;
public void increment() {
// to make sure the race condition is caused by the ++ and not by the assignment
synchronized (this) {
x = Integer.MAX_VALUE;
y = Integer.MAX_VALUE;
}
x++;
y++;
}
public synchronized void check() {
if (y > x) {
System.out.println("DataRace detected: y > x");
}
}
}
}