Is the != check thread safe? Is the != check thread safe? multithreading multithreading

Is the != check thread safe?


In the absence of synchronization this code

Object a;public boolean test() {    return a != a;}

may produce true. This is the bytecode for test()

    ALOAD 0    GETFIELD test/Test1.a : Ljava/lang/Object;    ALOAD 0    GETFIELD test/Test1.a : Ljava/lang/Object;    IF_ACMPEQ L1...

as we can see it loads field a to local vars twice, it's a non-atomic operation, if a was changed in between by another thread comparison may produce false.

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.


Is the check a != a thread-safe?

If a can potentially be updated by another thread (without proper synchronization!), then No.

I tried to program this and use multiple threads but didn't fail. I guess could not simulate race on my machine.

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.

Does this mean that a != a could return true.

Yes, in theory, under certain circumstances.

Alternatively, a != a could return false even though a was changing simultaneously.


Concerning the "weird behaviour":

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.

This "weird" behaviour is consistent with the following execution scenario:

  1. 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.

  2. 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 ...)

  3. 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:

This looks like this could be what C or C++ programmers calls "Undefined Behavior" (implementation dependent). Seems like there could be a few UB in java in corner cases like this one.

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:

  • when executed by a single thread, and
  • when executed by different threads that synchronize correctly (as per the Memory Model).

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:

public class MyTest {  private static Integer count=1;  @Test(threadPoolSize = 1000, invocationCount=10000)  public void test(){    count = new Integer(new Random().nextInt());    Assert.assertFalse(count != count);  }}

I have 2 fails on 10 000 invocations. So NO, it is NOT thread safe