I have a strange issue with this code:
class Test {
private static boolean test = false;
public static void main(String[] args) {
new Thread(() -> {
while (true) {
if (test) {
System.out.println("Print when breakpoint here!");
test = false;
}
}
}, "Thread1").start();
new Thread(() -> {
while (true) {
System.out.println("Print always");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
test = true;
}
}, " Thread2").start();
}
}
As I expected, since boolean test
is not volatile
, Thread1 uses local cache value of test
and when Thread2 changes it to true
Thread1 won't do anything. But when I put a breakpoint at the line System.out.println("Prints when put a breakpoint here!");
It will reaches there and prints the line! What's really happening by putting a breakpoint? Does it force the program to directly read the value of variable from memory? Or something else is happening?
As I expected, since boolean test is not volatile Thread1 uses local cache value of test and when Thread2 changes it to true Thread1 won't do anything.
Your expectation is incorrect.
According to the Java Language Specification, if one thread updates a non-volatile shared variable and another thread subsequently reads it without appropriate synchronization then the second thread may see the new value, or it may see an earlier value.
So what you are seeing is allowed by the JLS.
In practice, when a debug agent is attached to a JVM, it will typically cause the JIT compiler to recompile some or all methods at a lower optimization level ... or possibly even execute them using the bytecode interpreter. This is likely to happen for methods with breakpoints set in them, and when you are single-stepping1. This may result in different behavior for code that uses shared variables without proper synchronization when you debug it.
This is one of the reasons that debugging problems caused by inadequate synchronization is difficult.
As far as I know breakpoints change the instructions of code by adding a special trap called INT 3. So what's really going on?
That is what happens when you debug C / C++. It is not specified how a JVM handles this, but a typical JVM has other options for implementing breakpoints ... because of bytecodes and JIT compilation.
When I put a
sleep(1)
in theThread1
before if statement it will also print the line. Is there a same thing happening by adding a sleep?
The sleep
will cause the current thread to be suspended. What happens at the implementation level is not specified. However, it is likely that the native thread mechanisms will flush any outstanding writes (i.e. dirty cache entries) for the suspended thread to memory ... as part of the process of performing a thread context switch.
Similarly, if you use print statements, a typical I/O stack has internal synchronization that can trigger cache flushes, etc. This can also alter the behavior of the code that you are trying to debug.
However, I should stress that these behaviors are not specified.
1 - A JIT optimizer is allowed to reorder assignments provided that this doesn't alter single-threaded behavior. But, if you are debugging a method and observing that values of variables, the effects of the reordering are visible (to the programmer). De-optimizing / interpreting avoids this. Fortunately, a modern JVM / debug agent can do this "on the fly" as required.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With