Producer/Consumer in Java. Why do we need two conditions?
Asked Answered
T

2

6

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?

Trophic answered 11/4, 2016 at 5:31 Comment(0)
E
2

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.

Emotionalize answered 11/4, 2016 at 6:0 Comment(4)
Why doesn't the example in documentation use signalAll() instead of signal(), are they equivalent here since only one type of threads are waiting on one condition? signalAll is only required in case using the same condition for both producer/consumer?Busey
@SumitJain No, it's not really a question of how many conditions there are. It's more about how many threads can make progress once they're woken up. In this example, there can be multiple threads waiting in put on notFull.await(), but only one can make progress once it's woken up (because there will only be one spot in the queue to add to).Emotionalize
Agreed, but if we used one condition then we will have to use signalAll otherwise with signal it can also just wakeup the same type of thread, e.g. if called from take(), it can wake up another thread which calls take()Busey
@SumitJain In that sense, sure. The take thread wouldn't be able to proceed (because of the loop condition check), but there would be no more signals to wake up the put threads.Emotionalize
C
2

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-

  1. Interleaving of Tasks - We can have tasks interleaving - for example - one consume, one produce, one consume, one produce etc.
  2. 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.
Cyclostome answered 11/4, 2016 at 6:12 Comment(3)
How can 3 producer threads & 2 consumer threads be waiting for their chance at the same time?Trophic
Suppose you had 4 producer threads & 2 consumer threads and for instance, buffer is used by 1 producer thread, then the remaining threads are waiting for their chance to use the buffer.Cyclostome
@MaksimDmitriev, it is possible, in a bursty application, for one or more producers to completely fill up a queue before one of the consumers has had its turn to take something. Likewise, it is possible for one or more consumers to completely drain the queue before one producer has had a chance to put something. Either way, you can have producers and consumers waiting at the same time.Hop

© 2022 - 2024 — McMap. All rights reserved.