Reputation: 301
I'm trying to experiment with multithreading and following examples from here: https://docs.oracle.com/javase/specs/jls/se10/html/jls-8.html#jls-8.3.1.4
I've posted my code below.
Could you please help me to understand why data race happen for "if (x < y) {
" and not for "if (y > x) {
" ?
I'm using 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
Code:
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");
}
}
}
}
Output:
/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
Upvotes: 2
Views: 231
Reputation: 6314
akuzminykh already explained why if (x < y)
can be true. You also asked why you never see the same phenomenon when you do if (y > x)
.
The reason is that in java expressions are always evaluated from left to right and when you do y > x
, y
is always going to be loaded from memory first so x
was already incremented before y
and if x
is going to be read from a subsequent iteration it's also going to be larger than y
.
You can still see "DataRace detected" being printed when you do y > x
but that can happen if and only if x
is close to Integer.MAX_VALUE
and it overflows and becomes negative in subsequent iterations after y
was read from memory and only then x
is being read from memory.
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");
}
}
}
}
Upvotes: 1
Reputation: 4723
The comparison if (x < y) {
is not atomic.
t2
loads x
for the comparisont2
stops workingt1
increments x
and y
t1
stopst2
startst2
loads y
for the comparisonx
is the old value and y
is new, incremented, x < y
is true
.Here is an example of how to solve that with 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");
}
}
}
}
Upvotes: 4