class StackImpl
{
    private Object stackArray[];
    // volatile: compiler: do not optimize; write after every value change
    //    because threads may cache its value
    private volatile int topOfStack = -1;

    public StackImpl (int capacity) {  stackArray = new Object[ capacity ];  }

    public synchronized Object pop()
    {
        System.out.println( Thread.currentThread() + ": popping" );
        while ( isEmpty() )
        {
            try
            {
                System.out.println( Thread.currentThread() + ": waiting to pop" );
                wait();
            }
            catch ( InterruptedException e )  { }
        }
        Object obj = stackArray[ topOfStack ];
        stackArray[ topOfStack-- ] = null;
        System.out.println( Thread.currentThread() + ": notifying after pop" );
        notify();
        return obj;
    }

    public synchronized void push( Object element )
    {
        System.out.println( Thread.currentThread() + ": pushing" );
        while ( isFull() )
        {
            try
            {
                System.out.println( Thread.currentThread() + ": waiting to push" );
                wait();
            }
            catch (InterruptedException e )  { }
        }
        stackArray[ ++topOfStack ] = element;
        System.out.println( Thread.currentThread() + ": notifying after push" );
        notify();
    }

    public boolean isFull() {  return topOfStack == stackArray.length -1;  }

    public boolean isEmpty()  {  return topOfStack < 0;  }
}

abstract class StackUser extends Thread
{
    protected StackImpl stack;

    public StackUser( String threadName, StackImpl aStack )
    {
        super( threadName );
        stack = aStack;
        System.out.println( this );
        setDaemon( true ); // Daemon thread: dies no later than parent
        start();
    }
}

class StackPopper extends StackUser
{
    public StackPopper( String threadName, StackImpl aStack )
    {
        super( threadName, aStack );
    }

    public void run()
    {
        while (true)
        {
            stack.pop();
        }
    }
}

class StackPusher extends StackUser
{
    public StackPusher( String threadName, StackImpl aStack )
    {
        super( threadName, aStack );
    }

    public void run()
    {
        while (true)
        {
            stack.push(new Integer(1));
        }
    }
}

public class WaitAndNotifyTest
{
    public static void main( String args[] )
           throws InterruptedException
    {
        StackImpl stack = new StackImpl( 5 );

        new StackPusher( "A", stack );
        new StackPusher( "B", stack );
        new StackPopper( "C", stack );
        System.out.println( "Main Thread sleeping." );
        Thread.sleep( 200 );
        System.out.println( "Exit from Main Thread." );
    }
}