Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

notify() behaving like notifyAll() [duplicate]

I have one Calculator thread that calculates the sum of number from 1 to 50, and multiple Reader threads that show the result once Calculator thread is ready. I have an option of calling notify() and notifyAll() to signal the Reader threads that the result of calculation is ready to display. At LINE B of Calculator class, if I call the notifyAll() method, the result is printed 4 times as expected. But when I replace LINE B with just notify() still I see the result printed 4 times which does not seem to be correct. It is my understanding that notify() will only wakes up one of the threads that is waiting, not all. Why are all of the threads waking up and printing the result when I call notify?

public class ThreadWaitNotify {

    public static void main(String[] args) {
        Calculator c = new Calculator();
        Reader r = new Reader(c);
        Reader r2 = new Reader(c);
        Reader r3 = new Reader(c);
        Reader r4 = new Reader(c);

        r.start();
        r2.start();
        r3.start();
        r4.start();
        try {
            Thread.sleep(500L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        c.start();
    }

}

Reader class:

class Reader extends Thread {

    Calculator c;

    public Reader(Calculator c) {
        this.c = c;
    }

    @Override
    public void run() {
        synchronized (c) {
            try {
                System.out.println(Thread.currentThread().getName() + " Waiting for calculations: ");
                c.wait();    // LINE A
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " Total: " + c.getSum());
        }
    }
}

Calculator class:

class Calculator extends Thread {

    private int sum = 0;

    @Override
    public void run() {
        synchronized (this) {
            for (int i = 1; i <= 50; i++) {
                sum += i;
            }
            notify();  // LINE B
        }
    }

    public int getSum() {
        return sum;
    }
}

Output:

Thread-1 Waiting for calculations: 
Thread-4 Waiting for calculations: 
Thread-3 Waiting for calculations: 
Thread-2 Waiting for calculations: 
Thread-1 Total: 1275
Thread-2 Total: 1275
Thread-3 Total: 1275
Thread-4 Total: 1275

======================

UPDATE: Using an object as a monitor/lock instead of a Thread instance produces the correct behavior.

Updated ThreadWaitNotify Class:

public class ThreadWaitNotify {

    public static void main(String[] args) {
        Object monitor = new Object();
        Calculator c = new Calculator(monitor);
        Reader r = new Reader(c, monitor);
        Reader r2 = new Reader(c, monitor);
        Reader r3 = new Reader(c, monitor);
        Reader r4 = new Reader(c, monitor);

        r.start();
        r2.start();
        r3.start();
        r4.start();
        try {
            Thread.sleep(500L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        c.start();
    }

}

Updated Reader Class:

class Reader extends Thread {

    Calculator c;
    Object monitor;

    public Reader(Calculator c, Object monitor) {
        this.c = c;
        this.monitor = monitor;
    }

    @Override
    public void run() {
        synchronized (monitor) {
            try {
                System.out.println(Thread.currentThread().getName() + " Waiting for calculations: ");
                monitor.wait();   // LINE A
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " Total: " + c.getSum());
        }
    }
}

Updated Calculator Class:

class Calculator extends Thread {

    private int sum = 0;
    Object monitor;

    public Calculator(Object monitor) {
        this.monitor = monitor;
    }

    @Override
    public void run() {
        synchronized (monitor) {
            for (int i = 1; i <= 50; i++) {
                sum += i;
            }
            monitor.notify();       // LINE B
        }
    }

    public int getSum() {
        return sum;
    }
}
like image 304
Ali Avatar asked Mar 01 '17 12:03

Ali


2 Answers

It's not the notify() that wakes up all your reader Threads, but the end of the Calculator's Thread's lifespan.

I didn't know about this behaviour until now, too, but it seems that a terminating Thread will always wake up all Threads waiting for it. Just throw in another Thread.sleep() at the end of Calculator.run() and you'll see.

Update

There's a crucial difference I just realized reading John's answer.

The misunderstanding resides in the phrase 'waiting for it'. A Thread will indeed notify all waiters, but it has nothing to do with the Thread concept.

In fact it is a special behaviour, which is in particular, that a Thread, whenever it reaches the end of it's lifespan, will notify all waiters that are waiting on the Thread object itself. As John already pointed out, this will happen at some point 'after' Thread.exit(), hence inside the JVM, thus there is no relation to object deallocation.

Conclusion

Although this is most probably the reason for the described error, one should never rely on anything to happen inside the JVM. The fact this behaviour is that fairly documented is a definite indicator.

like image 149
Izruo Avatar answered Sep 18 '22 19:09

Izruo


My original answer was wrong. I just went through the native code to find out what is happening.

When a thread ends it will in fact notify all waiting threads on the exiting thread's monitor. Again, this is on the native level, and can change

thread.cpp ->

void JavaThread::exit(bool destroy_vm, ExitType exit_type) {
      ..... other code ....
      // Notify waiters on thread object. This has to be done after exit() is called
      // on the thread (if the thread is the last thread in a daemon ThreadGroup the
      // group should have the destroyed bit set before waiters are notified).
      ensure_join(this);
      assert(!this->has_pending_exception(), "ensure_join should have cleared");
     .... other code ....

This is from the JDK 7 source, but this functionality, I cannot imagine, will differ greatly.

like image 33
John Vint Avatar answered Sep 19 '22 19:09

John Vint