Q: Producer/Consumer in Java. Why do we need two conditions? I've read about the problem on https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/Condition.html.
This is what the documentation says about the two conditions:

We would like to keep waiting put threads and take threads in separate
  wait-sets so that we can use the optimization of only notifying a
  single thread at a time when items or spaces become available in the
  buffer. This can be achieved using two Condition instances.

I could notify a single thread using one condition since signal() wakes up one thread:
class BoundedBuffer2 {
    final Lock lock = new ReentrantLock();
    final Condition condition  = lock.newCondition();

    final Object[] items = new Object[100];
    int putptr, takeptr, count;

    public void put(Object x) throws InterruptedException {
        lock.lock();
        try {
            while (count == items.length)
                condition.await();
            items[putptr] = x;
            if (++putptr == items.length) putptr = 0;
            ++count;
            condition.signal();
        } finally {
            lock.unlock();
        }
    }

    public Object take() throws InterruptedException {
        lock.lock();
        try {
            while (count == 0)
                condition.await();
            Object x = items[takeptr];
            if (++takeptr == items.length) takeptr = 0;
            --count;
            condition.signal();
            return x;
        } finally {
            lock.unlock();
        }
    }
}

Why do we need two conditions?

A: Say your buffer was empty and two threads were waiting on take, blocked on 
condition.await();

Then a thread calls put. This will signal the Condition and wake up one of the take threads. That take thread will subsequently also signal the Condition, waking up the other take thread which will loop back in blocking state since there is nothing for it to take. 
That last signal was unnecessary. That second take thread should never have been woken up since nothing was available. 
The two condition algorithm allows you to notify only those threads that are relevant.

A: The reason for two conditions is to differentiate between threads which are producing items into the buffer and threads which are consuming items from the buffer.


*

*ProducingThread is bothered about notFull condition and is responsible for notEmpty condition, meaning that once it produces something, it has to raise the flag and say, 'hey, i have produced something, so it is not empty now.'

*ConsumingThread is bothered about notEmpty condition and is responsible for notFull condition , meaning that once it consumes something, it has to raise the flag and say, 'hey, i have consumed something, so it is not full now.'
By having this kind of separation of threads into two different wait-sets, we can alert one thread at a time - either a producer thread from producerThread set or a consumer thread from consumerThread set. By having a single condition, we might have a producer thread to alert another producer thread, which is not necessary or relevant.
If we have a single condition, which is used by both threads, we will not be able to differentiate which threads to give chance logically, for example, 3 producer threads & 2 consumer threads are waiting for their chance - either to add/remove item. If one of the consuming thread finishes its work, suppose it signals, then it is possible that another consuming thread might get woken up, which would not be our objective.
We might have different usecases of Producer/Consumer problem- 


*

*Interleaving of Tasks - We can have tasks interleaving - for example - one consume, one produce, one consume, one produce etc.

*Execute only relevant tasks - Producing threads need to be woken up only if producing task is necessary/possible and similarly Consuming threads need to be woken up only if consuming task is necessary/possible, otherwise the logic would become irrelevant/broken.

