java : wait() method keeps waiting even after calling notifyAll()

2k views Asked by At

Consider the following code

public class ThreadTest1
{
private static final long startTime = System.currentTimeMillis();

    public static void main(String args[])
    {
        Thread ct = new Thread(new ChildThread());
        ThreadTest1.print("starting child threads in MAIN");
        ct.start();
        synchronized(ct)
        {
            try
            {

            ThreadTest1.print("about to start wait() in MAIN");
            ct.wait();
            ThreadTest1.print("after wait() in MAIN");
            }
            catch(Exception e)
            {
            ThreadTest1.print("Exception in MAIN");
            }
        }
    }

    public static void print(String s)
    {
    System.out.println("Millisecond : "+(System.currentTimeMillis()-ThreadTest1.startTime)+"\t: "+s);
    }
}

class ChildThread implements Runnable
{
    public void run()
    {
        synchronized(this)
        {

        try
        {
        ThreadTest1.print("before thread notifyAll in CHILD");
        notifyAll();
        ThreadTest1.print("notifyAll over, sleep starts in CHILD");
        Thread.sleep(10000);
        ThreadTest1.print("after thread sleep in CHILD");

        }
        catch(Exception e)
        {
        ThreadTest1.print("Exception in CHILD");
        }
        ThreadTest1.print("End of run method in CHILD");
        }
    }
}

The ouput follows :

Millisecond : 12        : starting child threads in MAIN
Millisecond : 13        : about to start wait() in MAIN
Millisecond : 13        : before thread notifyAll in CHILD
Millisecond : 13        : notifyAll over, sleep starts in CHILD
Millisecond : 10015     : after thread sleep in CHILD
Millisecond : 10015     : End of run method in CHILD
Millisecond : 10016     : after wait() in MAIN

notifyAll() gets called at the 13th millisecond. But control comes out of wait() only at 10016th millisecond.

From the code given above, it appears as if the wait() call doesn't get over immediately after the notify() call.

But all documentations including the Java API, specify that the method calling wait() should get the lock immediately after the notify() call.

If wait() will not get over when notify() is called, then the need for notify() becomes void since the method calling wait() will automatically get control when the run method of the new thread gets over even if notify() is not called.

Waiting for someone to throw some light, if I am committing a mistake here.

4

There are 4 answers

2
Guillaume On BEST ANSWER

With the suggestions of other answers, here is a working implementation. Note that I also moved the sleep outside of the synchronized block. As a rule, synchronized blocks should always be as short as possible ...

public class ThreadTest {

    private static final long startTime = System.currentTimeMillis();

    public static void main(String args[]) {
        Thread ct = new ChildThread();
        ThreadTest.print("starting child threads in MAIN");
        ct.start();
        try {
            ThreadTest.print("about to start wait() in MAIN");
            synchronized (ct) {
                ct.wait();
            }
            ThreadTest.print("after wait() in MAIN");
        } catch (Exception e) {
            ThreadTest.print("Exception in MAIN");
        }
    }

    public static void print(String s) {
        System.out.println("Millisecond : " + (System.currentTimeMillis() - ThreadTest.startTime) + "\t: " + s);
    }

    private static final class ChildThread extends Thread {
        public void run() {
            try {
                ThreadTest.print("before thread notifyAll in CHILD");
                synchronized (this) {
                    notifyAll();
                }
                ThreadTest.print("notifyAll over, sleep starts in CHILD");
                Thread.sleep(1000);
                ThreadTest.print("after thread sleep in CHILD");

            } catch (Exception e) {
                ThreadTest.print("Exception in CHILD");
            }
            ThreadTest.print("End of run method in CHILD");
        }
    }
}
1
Nazgul On

2 problems here:

  1. wait and notify called on same objects and inside synchronized blocks which are synchronized on same objects. Yours are different. One is on a thead named ct and another is on a Runnable of type ChildThread.

  2. you are calling start before you wait. It is very much possible that notify is called before the main thread begins to wait. So you may miss the notify. After fixing point 1 try to adjust the code so that you minimize the wait and notify gap.

0
Jim Garrison On

As others have noted, the two threads are synchronizing on different objects. So the question becomes how to explain the 10-second delay in the main method.

Notice that if you change ChildThread to remove the synchronized block and notifyAll(), you get the same behavior.

class ChildThread implements Runnable
{
    public void run()
    {
        System.out.println(this);
//        synchronized (this)
//        {
            try
            {
//                MiscTest.print("before thread notifyAll in CHILD");
//                notifyAll();
//                MiscTest.print("notifyAll over, sleep starts in CHILD");
                Thread.sleep(5000);
                MiscTest.print("after thread sleep in CHILD");

            }
            catch (Exception e)
            {
                MiscTest.print("Exception in CHILD");
            }
            MiscTest.print("End of run method in CHILD");
//        }
    }

This indicates that, when a Thread ends, it does an implicit notifyAll() on its own Thread object, and any other threads waiting specifically on the Thread object are allowed to proceed. I was unable to find any documentation stating this, but clearly that is what is happening.

4
Stephen C On

The problem is that you are notifying and waiting on different objects. You wait() on the Thread and in the run() method you are calling on this ... which is a ChildThread.


This is obscured by the fact that you've misnamed your ChildThread class. That name implies that it is a Thread subclass, but it is actually a Runnable subclass.