I know that compound operations such as i++
are not thread safe as they involve multiple operations.
But is checking the reference with itself a thread safe operation?
a != a //is this thread-safe
I tried to program this and use multiple threads but it didn't fail. I guess I could not simulate race on my machine.
EDIT:
public class TestThreadSafety {
private Object a = new Object();
public static void main(String[] args) {
final TestThreadSafety instance = new TestThreadSafety();
Thread testingReferenceThread = new Thread(new Runnable() {
@Override
public void run() {
long countOfIterations = 0L;
while(true){
boolean flag = instance.a != instance.a;
if(flag)
System.out.println(countOfIterations + ":" + flag);
countOfIterations++;
}
}
});
Thread updatingReferenceThread = new Thread(new Runnable() {
@Override
public void run() {
while(true){
instance.a = new Object();
}
}
});
testingReferenceThread.start();
updatingReferenceThread.start();
}
}
This is the program that I am using to test the thread-safety.
Weird behavior
As my program starts between some iterations I get the output flag value, which means that the reference !=
check fails on the same reference. BUT after some iterations the output becomes constant value false
and then executing the program for a long long time does not generate a single true
output.
As the output suggests after some n (not fixed) iterations the output seems to be constant value and does not change.
Output:
For some iterations:
1494:true
1495:true
1496:true
19970:true
19972:true
19974:true
//after this there is not a single instance when the condition becomes true
If
a
can potentially be updated by another thread (without proper synchronization!), then No.That doesn't mean anything! The issue is that if an execution in which
a
is updated by another thread is allowed by the JLS, then the code is not thread-safe. The fact that you cannot cause the race condition to happen with a particular test-case on a particular machine and a particular Java implementation, does not preclude it from happening in other circumstances.Yes, in theory, under certain circumstances.
Alternatively,
a != a
could returnfalse
even thougha
was changing simultaneously.Concerning the "weird behaviour":
This "weird" behaviour is consistent with the following execution scenario:
The program is loaded and the JVM starts interpreting the bytecodes. Since (as we have seen from the javap output) the bytecode does two loads, you (apparently) see the results of the race condition, occasionally.
After a time, the code is compiled by the JIT compiler. The JIT optimizer notices that there are two loads of the same memory slot (
a
) close together, and optimizes the second one away. (In fact, there's a chance that it optimizes the test away entirely ...)Now the race condition no longer manifests, because there are no longer two loads.
Note that this is all consistent with what the JLS allows an implementation of Java to do.
@kriss commented thus:
The Java Memory Model (specified in JLS 17.4) specifies a set of preconditions under which one thread is guaranteed to see memory values written by another thread. If one thread attempts to read a variable written by another one, and those preconditions are not satisfied, then there can be a number of possible executions ... some of which are likely to be incorrect (from the perspective of the application's requirements). In other words, the set of possible behaviours (i.e. the set of "well-formed executions") is defined, but we can't say which of those behaviours will occur.
The compiler is allowed to combine and reorder loads and save (and do other things) provided the end effect of the code is the same:
But if the code doesn't synchronize properly (and therefore the "happens before" relationships don't sufficiently constrain the set of well-formed executions) the compiler is allowed to reorder loads and stores in ways that would give "incorrect" results. (But that's really just saying that the program is incorrect.)
Proved with test-ng:
I have 2 fails on 10 000 invocations. So NO, it is NOT thread safe
Regarding the weird behaviour:
Since the variable
a
is not marked asvolatile
, at some point it might value ofa
might be cached by the thread. Botha
s ofa != a
are then the cached version and thus always the same (meaningflag
is now alwaysfalse
).No, it is not. For a compare the Java VM must put the two values to compare on the stack and run the compare instruction (which one depends on the type of "a").
The Java VM may:
false
In the 1st case, another thread could modify the value for "a" between the two reads.
Which strategy is chosen depends on the Java compiler and the Java Runtime (especially the JIT compiler). It may even change during the runtime of your program.
If you want to make sure how the variable is accessed, you must make it
volatile
(a so called "half memory barrier") or add a full memory barrier (synchronized
). You can also use some hgiher level API (e.g.AtomicInteger
as mentioned by Juned Ahasan).For details about thread safety, read JSR 133 (Java Memory Model).
It has all been well explained by Stephen C. For fun, you could try to run the same code with the following JVM parameters:
This should prevent the optimisation done by the JIT (it does on hotspot 7 server) and you will see
true
forever (I stopped at 2,000,000 but I suppose it continues after that).For information, below is the JIT'ed code. To be honest, I don't read assembly fluently enough to know if the test is actually done or where the two loads come from. (line 26 is the test
flag = a != a
and line 31 is the closing brace of thewhile(true)
).In the absence of synchronization this code
may produce
true
. This is the bytecode fortest()
as we can see it loads field
a
to local vars twice, it's a non-atomic operation, ifa
was changed in between by another thread comparison may producefalse
.Also, memory visibility problem is relevant here, there is no guarantee that changes to
a
made by another thread will be visible to the current thread.