javajava-threads

Confusion about thread synchronization


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?


Solution

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