public class Interview implements Runnable {
int b = 100;
public synchronized void m1() throws Exception {
//System.out.println("-----");
b = 1000;
Thread.sleep(5000);
System.out.println("b = " + b);
}
public synchronized void m2() throws Exception {
Thread.sleep(2500);
b = 2000;
}
@Override
public void run() {
try {
m1();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) throws Exception {
Interview interview = new Interview();
Thread thread = new Thread(interview);
thread.start();
interview.m2();
System.out.println(interview.b);
}
}
When I comment out the line "System.out.println(" --");"
The output is:
1000
b = 1000
When I don't comment out the line "System.out.println(" --");"
The output is:
2000
-----
b = 1000
Why is the value of interview.b
different?
It all depends on the order of execution. m1()
and m2()
can only run after one another but System.out.println(interview.b);
can run parallely to m1()
.
While it cannot be guaranteed it's likely that m2()
will run before the other thread spins up and starts m1()
so b
would have the value 2000 right before execution reaches System.out.println(interview.b);
.
(Why is m2()
likely to run before m1()
? Because creating a thread context takes some time during which m2()
may have already acquired the synchronization lock so m1()
has to wait. Add a Thread.sleep(1)
before calling m2()
and m1()
is likely to start first).
And here's where the fun starts: m1()
is likely to continue right when m2()
releases the synchronization lock so it'll run in parallel with System.out.println(interview.b);
. Without System.out.println("-----");
the first thing m1()
will do is set b
to 1000 which may happen before System.out.println(interview.b);
which is why you see 1000 printed instead of 2000. But it you add the print statement then b = 1000
is delayed and thus System.out.println(interview.b);
will still print 2000.
Let me try to illustrate.
Without System.out.println("-----");
:
start sub thread
| m2() acquires lock
| / after sleeping b=2000 and then m2() releases the lock
main // / print(b) -> 1000
| // / /
Main thread: o---+---++-----+--+-x
Sub thread: \++-----++----+-x
|| || \
/ | |\ print("b=" + b) ->b=1000
create thread context | | b=1000
m1() starts but has to wait \m1() acquires lock
With System.out.println("-----");
:
start sub thread
| m2() acquires lock
| / after sleeping b=2000 and then m2() releases the lock
main // / print(b) -> 2000
| // / /
Main thread: o---+---++-----+--+-x
Sub thread: \++-----++-+----+-x
|| || | \
/ | || \ print("b=" + b) ->b=1000
create thread context | | \ b=1000
| | print("----")
m1() starts but has to wait \m1() acquires lock
This may sound a little vague with all the "likely", "may" etc. and that's the issue with concurrency / multithreading: parallel execution may result in different perceived order of operations (theoretically they'd happen at the same time but of course that's not true in reality - one may be faster than the other) and JVM optimizations may change some ordering as well (although the "happens before" guarantee is still maintained).