Reputation: 3381
I have a small example application with two threads A
and B
. Both spend some CPU cycles before each setting a value on one volatile variable (A
sets the value for x
, B
sets the value for y
), then B
also prints out the two variables. When this game is repeated a number of times then sometimes the value of x
is visible in B
but sometimes not (even if the x
and y
are both volatile). Why is that?
public class Vol2 {
public static volatile int x = 0;
public static volatile int y = 0;
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 50; i++) {
x = 0;
y = 0;
Thread t1 = new Thread(() -> {
doWork();
x = 5;
});
Thread t2 = new Thread(() -> {
doWork();
y = 6;
System.out.println("x: " + x + ", y: " + y);
});
t1.start();
t2.start();
t1.join();
t2.join();
}
}
public static void doWork() {
int s = 0;
for (int i = 0; i < 1_000_000; i++) {
s += i;
}
}
}
Upvotes: 1
Views: 68
Reputation: 120858
There are subtle problems with your example. First is that doWork
might be entirely useless, it does not have any side-effects and JIT
can eliminate that entirely. Think about it: since that loop is purely a local thing that no one sees, why do it in the first place?
Then, you have somehow a wrong understanding about Thread::start
, which this answer already explains to you. t1.start();
schedules a thread to be started, it does not mean that it will finish (or even start) before t2.start();
.
Then, at least IMHO, you are using the wrong tool for the job. You need jcstress, a tool tailored by experts for this kind of things. Here is how your code is going to look like using it:
@JCStressTest
@State
@Outcome(id = "0, 0", expect = Expect.FORBIDDEN, desc = "can not happen")
@Outcome(id = "0, 6", expect = Expect.ACCEPTABLE, desc = "writerY only")
@Outcome(id = "5, 6", expect = Expect.ACCEPTABLE, desc = "both")
public class TwoThreads {
volatile int x = 0;
volatile int y = 0;
@Actor
void writerX(II_Result r) {
x = 5;
}
@Actor
void writerY(II_Result r) {
y = 6;
r.r1 = x;
r.r2 = y;
}
}
How to run that and what it means - is an exercise to you, but the main point is that running this will only give you two possible outcomes:
5, 6 --> meaning writerX finished its work before writerY
or translated to your code, t1
before t2
.
0, 6 --> meaning writerY finished its work before writerX
and as such that x = 5
by writerX
was lost and never recorded. Or, translated to your code, t2
finished before t1
.
Upvotes: 0
Reputation: 40044
Try it like this. Assign the variable and then do some work. That gives each thread a chance to set the value. Threads always take a finite amount of time to start and start asynchronously, so events at the beginning can't be predicted.
public class Vol2 {
public static volatile int x = 0;
public static volatile int y = 0;
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 50; i++) {
x = 0;
y = 0;
Thread t1 = new Thread(() -> {
x = 5;
doWork();
});
Thread t2 = new Thread(() -> {
y = 6;
doWork();
System.out.println("x: " + x + ", y: " + y);
});
t1.start();
t2.start();
t1.join();
t2.join();
}
}
public static void doWork() {
int s = 0;
try {
Thread.sleep(10);
} catch (InterruptedException ie){}
}
}
Upvotes: 0
Reputation: 147164
Threads t1
and t2
may execute at the same time. There's no guarantee that t1
will have assigned x
before t2
has assigned y
and read x
.
Upvotes: 1