简体   繁体   中英

When and How to use wait() and notify()

I found this example from SO. Now, I was trying to understand the usage of wait() and notify()/notifyAll() . In which scenario and why do we need this.

class BlockingQueue<T> {

    private Queue<T> queue = new LinkedList<T>();
    private int capacity;

    public BlockingQueue(int capacity) {
        this.capacity = capacity;
    }

    public synchronized void put(T element) throws InterruptedException {
        while (queue.size() == capacity) {
             System.out.println("Waiting...");
            wait();

        }

        queue.add(element);
        notify(); // notifyAll() for multiple producer/consumer threads
    }

    public synchronized T take() throws InterruptedException {
        while (queue.isEmpty()) {
            wait();
        }

        T item = queue.remove();
        notify(); // notifyAll() for multiple producer/consumer threads
        return item;
    }
}

So, implemented Runnable and overriden run() method like below

 @Override
    public void run() {
        // synchronized (this) {
        BlockingQueue<Integer> s = new BlockingQueue(10);
        for (int i = 0; i < 12; i++) {
            try {
                s.put(i);
                if (i > 9) {
                    System.out.println(Thread.currentThread().getName() + "  : " + s.take());
                }
                System.out.println(Thread.currentThread().getName() + " ExtendsThread : Counter : " + i);
            } //}
            //notify();
            catch (InterruptedException ex) {
                Logger.getLogger(ExtendsThread.class.getName()).log(Level.SEVERE, null, ex);
            }

        }

    }

And, running the thread like below

 ImplementsRunnable rc = new ImplementsRunnable();
        Thread t1 = new Thread(rc, "A");
        t1.start();

When I'm running it, then it is stuck after counter : 9 and keep on waiting for forever. Anyone suggest me what's wrong here ?

Your concept is slightly flawed. The BlockingQueue can act as bridge in a producer/consumer pattern.

That is, it allows one thread to write content to it and another thread to read content from it, but it's doing it in such away that if:

  • There are no items to be taken, it waits until new items arrive
  • If there are too many items, it waits for items to be removed

In this case, the wait and notify are internal messaging for the instance of the BlockingQueue

You can have a look at Intrinsic Locks and Synchronization .

So, instead of using just one thread, you should be using (at least) two, a produce and a consumer...

Producer

This takes an instance of BlockingQueue and adds int values to it. Each time it stops for 1 second before adding the next

public class Producer implements Runnable {

    private BlockingQueue<Integer> queue;

    public Producer(BlockingQueue<Integer> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        for (int index = 0; index < 10; index++) {
            try {
                System.out.println("Put " + index);
                queue.put(index);
                Thread.sleep(1000);
            } catch (InterruptedException ex) {
            }
        }
    }

}

Consumer

The consumer takes a BlockQueue and reads int values from it, which be blocked until a value exists.

public class Consumer implements Runnable {

    private BlockingQueue<Integer> queue;

    public Consumer(BlockingQueue<Integer> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        try {
            while (true) {
                Integer value = queue.take();
                System.out.println("Took " + value);
            }
        } catch (InterruptedException ex) {
            Logger.getLogger(JavaApplication220.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

}

You can get these start using something like...

BlockingQueue bq = new BlockingQueue(10);
Thread p = new Thread(new Producer(bq));
Thread c = new Thread(new Consumer(bq));
c.setDaemon(true);
c.start();
p.start();

You should note that there is a small delay between the put messages, but almost no delay between the took messages. This is the queue in action. The Consumer is blocking/waiting on the queue to have something to give it.

You can play around with the Producer and Consumer , maybe changing their times (having a longer delay in the Consumer before taking an item for example) to see how this might cause different effects

When I'm running it, then it is stuck after counter : 9 and keep on waiting for forever

This is likely because you've exceeded the capacity of the queue and it's put method is blocking until you take something from it (you essentially have a dead lock, where the queue is waiting for you to take something from it, but you can't because you're locked on the put )

Things to remember:

  • For two or more threads to work with monitor locks, they MUST share the same instance of the monitor/object lock. In this case, the same instance of BlockingQueue
  • notify will wake one object whose is waiting on the same instance of the monitor lock's wait method. There is no way to know which one. This can be useful if you have multiple consumers, but don't care about the order in which the data is processed, for example

Updated with additional example

So, this takes the Thread.sleep out of the Producer (and allows the producer to produce 100 values) and adds a Thread.sleep to the Consumer .

This way, the Producer will reach it's capacity before the Consumer can drain it, forcing it to wait until the Consumer can take values from it...

public class Producer implements Runnable {

    private BlockingQueue<Integer> queue;

    public Producer(BlockingQueue<Integer> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        for (int index = 0; index < 100; index++) {
            try {
                System.out.println("Put " + index);
                queue.put(index);
            } catch (InterruptedException ex) {
            }
        }
    }

}

public class Consumer implements Runnable {

    private BlockingQueue<Integer> queue;

    public Consumer(BlockingQueue<Integer> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        try {
            while (true) {
                Integer value = queue.take();
                System.out.println("Took " + value);
                Thread.sleep(1000);
            }
        } catch (InterruptedException ex) {
        }
    }

}

Add printlns here

public synchronized void put(T element) throws InterruptedException {
    while (queue.size() == capacity) {
        System.out.println("blocked");
        wait();
    }
    queue.add(element);
    notify(); // notifyAll() for multiple producer/consumer threads
    System.out.println("put "+ element);
}

public synchronized T take() throws InterruptedException {
    while (queue.isEmpty()) {
        wait();
    }
    T item = queue.remove();
    notify(); // notifyAll() for multiple producer/consumer threads
    System.out.println("removed " + item);
    return item;
}

and run this test

public static void main(String argv[]) throws Exception {
    final BlockingQueue q = new BlockingQueue(2);
    new Thread() {
        public void run() {
            try {
                Thread.sleep(5000);
                q.take();
            } catch (Exception e) {
                e.printStackTrace();
            }
        };
    }.start();
    q.put(1);
    q.put(2);  // will block here until tread 2 takes an element and reduces the capacity
    q.put(3);   
}

it will print

put 1
put 2
blocked
removed 1
put 3

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM