11

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;
    }
}
Ali
  • 1,442
  • 1
  • 15
  • 29
  • Thanks for taking a look @OldCurmudgeon. The code is already posted under the main method of ThreadWaitNotify. I am creating 4 Reader instances. – Ali Mar 01 '17 at 12:48
  • I wouldn't say that the updated version is "correct" because you're relying on undocumented behavior in both cases. In one case it seems to fit your idea of how the code should work, but that's coincidental. Your assumption that wait only returns when another thread invokes notify is incorrect. – Willis Blackburn Mar 01 '17 at 13:50
  • 3
    Locking on a `Thread` is an antipattern. Extending `Thread` is an antipattern. – Lew Bloch Mar 01 '17 at 16:11
  • One call to `wait` must return when when `notify` is called, but `wait` is also allowed to return for any reason it feels like. – user253751 Mar 01 '17 at 22:25

5 Answers5

11

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.

Community
  • 1
  • 1
Izruo
  • 2,246
  • 1
  • 11
  • 23
  • I think you are on to something. I added a 5 second delay using Thread.sleep outside of synchronized block and saw only one total being displayed. After the 5 seconds were over, the remaining three messages displayed. Do you have a reference for the claim that Thread will always wakeup all threads wait for it if its terminated? – Ali Mar 01 '17 at 13:08
  • 1
    you're right, even if you completely remove `notify()` all Threads will weak up at the end on the Calculator-Thread – JohnnyAW Mar 01 '17 at 13:09
  • 1
    @Ali No, as badass as this may sound, I had this idea on my own and just tested it, so no reference here. Sorry! – Izruo Mar 01 '17 at 13:10
  • 1
    @Ali if you use an ordinary `Object` as a lock, you will see the difference between `notify()` and `notifyAll()` – JohnnyAW Mar 01 '17 at 13:15
  • Your statement "a terminating thread will always wake up all threads waiting for it" can't be true because when the thread terminates, nothing is waiting for it. It's already exited the synchronized block. It's just another thread running without any locks. – Willis Blackburn Mar 01 '17 at 13:16
  • Wow, your "discovery" is literally documented as the behavior of *synchronized*. Just a simple search for the first result was enough https://docs.oracle.com/javase/tutorial/essential/concurrency/syncmeth.html – iheanyi Mar 01 '17 at 14:09
  • 1
    It's not related to the `Thread`, it's related to an object monitor, which in this particular case is an instance of `Thread`. It seems indeed that object deallocation causes a *spurious wakeup* or an internal notifyAll. Java Memory Model to the rescue with explanation of possible wake-ups: https://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.2 – bashnesnos Mar 01 '17 at 15:47
  • @iheanyi I don't see anything relevant to the discussion. What are you referring to? – shmosel Mar 01 '17 at 17:05
  • @shmosel "First, it is not possible for two invocations of synchronized methods on the same object to interleave. When one thread is executing a synchronized method for an object, all other threads that invoke synchronized methods for the same object block (suspend execution) until the first thread is done with the object." So, it should be expected that once Calculator completes, everything else synchronized on it will unblock and run. – iheanyi Mar 01 '17 at 17:26
  • @iheanyi `So, it should be expected that once Calculator completes, everything else synchronized on it will unblock and run. –` Your link doesn't come to that conclusion. A Thread is special in that when it completes, the thread itself will notify all other threads waiting on it's own monitor. That is the "discovery" Izuro is referring to. The phrase "done with the object" only refers to a thread exiting a synchronized block and has nothing to do with a thread ending. – John Vint Mar 01 '17 at 21:00
  • 3
    @bashnesnos note the Thread is *not* being deallocated. – user253751 Mar 01 '17 at 22:24
  • 1
    @bashnesnos Did another update - hope it's clear now. – Izruo Mar 02 '17 at 00:54
  • @immibis well it would be deallocated (garbage collected) at some point after the `exit`; I just assumed wrong that it notifies it's wait set on deallocation rather than on `exit`. – bashnesnos Mar 02 '17 at 07:21
  • 1
    @bashnesnos It won't be garbage collected as long as something is `wait`ing for it. Because the thing that's `wait`ing for it has a reference to it (how else would it call `wait`?). – user253751 Mar 02 '17 at 20:25
  • @immibis it's a little bit confusing though that waiting occurs on the `ObjectMonitor`, rather than on the object itself, so it's and indirect reference. But it's still a reference so you're right. – bashnesnos Mar 03 '17 at 12:35
  • 1
    @bashnesnos You have to have a reference to the object in order to call `wait` on it. Even if `wait` internally doesn't use the whole object. – user253751 Mar 03 '17 at 22:32
9

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.

John Vint
  • 39,695
  • 7
  • 78
  • 108
4

Apart from what others have written, I didn't analyse code thoroughly, but, as far as your wait code goes, do you account for spurious wakeups? Your waiting threads can be woken up not only by calls to notify/notifyAll but undeterministically. That's why you should always call wait in a loop, like so:

while (!condition) { obj.wait(); }

See this question for details.

Community
  • 1
  • 1
lukeg
  • 4,189
  • 3
  • 19
  • 40
0

Izruo's answer is right. I would like to add something to it. If you will place sleep() outside the synchronized block, then one of the thread will wake up and other thread's will be in waiting state.

Aman Goyal
  • 383
  • 2
  • 9
  • Thanks for your response @AmanGoyal. Thats not how the code functions. See the updated code above. The code behaves differently if you call wait and notify on an instance of Thread Class compared to instance of Object Class. Using the object instance if you call notify() instead of notifyAll() the program hangs (the remaining threads stay in waiting state). – Ali Mar 01 '17 at 13:38
  • Agree. I misunderstood the behavior. I was trying to understand it from jConsole. That's really a very good question. – Aman Goyal Mar 01 '17 at 13:42
-1

Calling notify doesn't start any of the Reader threads running. All it does is select a Reader and remove it from the wait set for Calculator, which enables it to obtain the Calculator monitor once Calculator has released it. Calculator continues running, leaving the synchronized block and then exiting.

Upon leaving the synchronized block, the selected Reader starts running, prints its total, and also leaves the synchronized block and exits.

At this point nobody owns the monitor, and so in theory every thread is blocked. But the Java Language Specification permits the JVM to wake up blocked threads without anyone calling notify: "The thread may be removed from the wait set due to ... An internal action by the implementation. Implementations are permitted, although not encouraged, to perform 'spurious wake-ups', that is, to remove threads from wait sets and thus enable resumption without explicit instructions to do so." They don't start running (they're in a synchronized block and must still acquire the lock), but they do become eligible to run. That must be what is happening here.

More information on this: Coder Ranch thread, DevGuli

The theory that it is a thread exiting that causes the wakeup may be true, but only because it is the precipitating event of the spurious wakeup.

Willis Blackburn
  • 8,068
  • 19
  • 36
  • 1
    no, it has nothing to do with "JVM realizing something", if you use an ordinary `Object` as a lock, you will end up with 3 blocked Threads – JohnnyAW Mar 01 '17 at 13:17
  • There is something special going on when we use Thread instance's wait and notify. If i use Object as a monitor and call the wait() and notify() method on it, only one thread prints the total the remaining threads hang until i stop the program. So I disagree with your statement that 'JVM would realize that in order for the program to have any hope of continuing, it needs to resume one of the Reader threads...' – Ali Mar 01 '17 at 13:20
  • 1
    My theory is wrong--keeping the Calculator thread alive doesn't change the behavior. – Willis Blackburn Mar 01 '17 at 13:23