Search code examples
javamultithreadingwaitnotify

Calling Java notify() within an if condition


I have just written a simple java example to get familiar with the concept of wait and notify methods.

The idea is that when calling notify(), the main thread will print the sum.

MyThread class

public class MyThread extends Thread {
    public int times = 0;

    @Override
    public void run() {

        synchronized (this) {
            try {
                for (int i = 0; i < 10; i++) {
                    times += 1;
                    Thread.sleep(500);
                    if (i == 5) {
                        this.notify();
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

Main Class

public class Main {

    public static void main(String[] args) {

        MyThread t = new MyThread();
        synchronized (t) {
            t.start();
            try {
                t.wait();
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println(t.times);
        }
    }
}

Expected Results

5 but I got 10 instead.

Well, what I though is that when notify() is called, the main thread will wakeup and execute the System.out.println(t.times) which should give 5. Then the run() will continue till it finishes the for loop which will update the value of times to 10.

Any help is highly appreciated.


Solution

  • Synchronized blocks imply mutual exclusion. At any given moment, only one thread is allowed to hold the lock and execute the code within a synchronized block. This rule spreads over all the blocks guarded by the same lock.

    In your case, there're two such blocks that use the same lock, so it's either the main thread or the MyThread that is allowed to execute code in either of these blocks, the other thread must wait. So, you have the following scenario here:

    1. The main thread acquires the lock.
    2. The main thread starts the second thread.
    3. The second thread hits the synchronized block but cannot enter it since the lock is being hold by the main thread.
    4. The main thread calls wait(). This call releases the lock and puts the main thread into the WAITING state.
    5. The second thread now can acquire the lock and enter the synchronized block.
    6. The second thread counts to five and calls notify(). This call doesn't release the lock, it just notifies the main thread that it can progress as soon as it can reacquire the lock.
    7. The main thread awakes but it cannot make progress because it cannot reacquire the lock (it's still being hold by the second thread). Remember, no two threads can be active within a synchronized block guarded by the same lock at once, and now, the second thread is still active, so the main one must continue waiting.
    8. The second thread continues counting, sets times to 10 and eventually leaves the synchronized block, releasing the lock.
    9. The main thread reacquires the lock and can now make progress to the println. But by this time, the times is already 10.

    Using join() won't help you either because the result will be the same – the main thread can only make progress when the second one is finished.

    If you want your main thread to continue execution as soon as the second thread hits 5, you need to acquire the lock and release it immediately after that event:

    public class MyThread extends Thread {
        public volatile int times = 0;
    
        @Override
        public void run() {
            try {
                for (int i = 0; i < 10; i++) {
                    times += 1;
                    Thread.sleep(500);
                    if (i == 5) {
                        synchronized(this) {
                            this.notify();
                        }
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    

    Don't forget to make times volatile, otherwise JVM won't guarantee that you'll see its actual value in your main thread.

    And you should also understand that this approach doesn't guarantee that your main thread prints 5. It might occur that by the time it reaches the println call, the second thread makes one or two or even more iterations and you'll see something greater than 5 (though it's highly unluckily due to the sleep() call on every iteration).