1

When I first read about interface BlockingQueue I read that: Producer blocks any more put() calls in a queue if it has no more space. And the opposite, it blocks method take(), if there are no items to take. I thought that it internally works same as wait() and notify(). For example, when there are no more elements to read internally wait() is called until Producer adds one more and calls notify()..or that's what we would do in 'old producer/consumer pattern. BUT IT DOESN'T WORK LIKE THAT IN BLOCKING QUEUE. How? What is the point? I am honestly surprised!

I will demonstrate:

public class Testing {
    BlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<>(3);

     synchronized void write() throws InterruptedException {
        for (int i = 0; i < 6; i++) {
            blockingQueue.put(i);
            System.out.println("Added " + i);
            Thread.sleep(1000);
        }
    }

    synchronized void read() throws InterruptedException {
        for (int i = 0; i < 6; i++) {
            System.out.println("Took: " + blockingQueue.take());
            Thread.sleep(3000);
        }
    }
}

class Test1 {
    public static void main(String[] args) {
        Testing testing = new Testing();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    testing.write();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    testing.read();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

OUTPUT:

Added 0
Added 1
Added 2

'program hangs'.

My questions is how does take() and put() BLOCK if they don't use wait() or notify() internally? Do they have some while loops that burns CPU circles fast? I am frankly confused.

Ana Maria
  • 475
  • 2
  • 11
  • 2
    Have you checked out the source code of `ArrayBlockingQueue`? – akuzminykh Aug 16 '20 at 20:53
  • I did of course. It says it would await if list is full. My Producer never actually calls await(), which is why Consumer doesn't get a chance to do anything. I am confused.. Or, from different perspective my Producer never leaves a lock! – Ana Maria Aug 16 '20 at 21:10
  • This is a good question - i'd be interested to know the answer as well, not sure why someone voted to close it. – Woody Aug 16 '20 at 21:28

3 Answers3

2

Here's the current implementation of ArrayBlockingQueue#put:

/**
 * Inserts the specified element at the tail of this queue, waiting
 * for space to become available if the queue is full.
 *
 * @throws InterruptedException {@inheritDoc}
 * @throws NullPointerException {@inheritDoc}
 */
public void put(E e) throws InterruptedException {
    Objects.requireNonNull(e);
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        while (count == items.length)
            notFull.await();
        enqueue(e);
    } finally {
        lock.unlock();
    }
}

You'll see that, instead of using wait() and notify(), it invokes notFull.await(); where notFull is a Condition.

The documentation of Condition states the following:

Condition factors out the Object monitor methods (wait, notify and notifyAll) into distinct objects to give the effect of having multiple wait-sets per object, by combining them with the use of arbitrary Lock implementations. Where a Lock replaces the use of synchronized methods and statements, a Condition replaces the use of the Object monitor methods.

Jacob G.
  • 28,856
  • 5
  • 62
  • 116
  • Thanks, but this made it much worse. I am even more confused hahaha. It says it would await (leave lock) if queue is full. But in my case lock is never 'left' when my queue is full. That is why my Consumer thread never gets a chance to take() elements. So how does it happen? – Ana Maria Aug 16 '20 at 21:09
  • @AnaMaria `BlockingQueue` is already thread-safe; why would you need additional synchronization? – Jacob G. Aug 16 '20 at 21:11
  • True, but I like to experiment. That is how I learn the most :) – Ana Maria Aug 16 '20 at 21:12
0

If you go through below code, you will get an idea that how producer/consumer problem will get resolve using BlokingQueue interface.

Here you are able to see that same queue has been shared by Producer and Consumer.

And from main class you are starting both thread Producer and Consumer.

class Producer implements Runnable {
    protected BlockingQueue blockingQueue = null;

    public Producer(BlockingQueue blockingQueue) {
        this.blockingQueue = blockingQueue;
    }

    @Override
    public void run() {
        for (int i = 0; i < 6; i++) {
            try {
                blockingQueue.put(i);
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Added " + i);
        }
    }
}

class Consumer implements Runnable {

    protected BlockingQueue blockingQueue = null;

    public Consumer(BlockingQueue blockingQueue) {
        this.blockingQueue = blockingQueue;
    }

    @Override
    public void run() {
        for (int i = 0; i < 6; i++) {
            try {
                System.out.println("Took: " + blockingQueue.take());
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class Test1 {
    public static void main(String[] args) throws InterruptedException {

        BlockingQueue queue = new ArrayBlockingQueue(3);

        Producer producer = new Producer(queue);
        Consumer consumer = new Consumer(queue);

        new Thread(producer).start();
        new Thread(consumer).start();

        Thread.sleep(4000);
    }
}

This code will print output like

Took: 0
Added 0
Added 1
Added 2
Took: 1
Added 3
Added 4
Took: 2
Added 5
Took: 3
Took: 4
Took: 5
Sagar Gangwal
  • 7,544
  • 3
  • 24
  • 38
  • Thanks, I knew how to do that. The issue is how it doesn't work in synchronized write and read methods. Yours are not synchronized and that is why is works :) Because I thought that it uses same wait() and notify() we all use explicitly. – Ana Maria Aug 16 '20 at 21:03
0

(I'm sure some or all parts of my answer could be something that you have already understood, in that case, please just consider it as a clarification :)).

1. Why did your code example using BlockingQueue get to ‘program hangs’?

1.1 Conceptually
First of all, if we can leave out the implementation level detail such as ‘wait()’, ‘notify()’, etc for a second, conceptually, all implementation in JAVA of BlockingQueue do work to the specification, i.e. like you said:

‘Producer blocks any more put() calls in a queue if it has no more space. And the opposite, it blocks method take(), if there are no items to take.’

So, conceptually, the reason that your code example hangs is because

1.1.1.
the thread calling the (synchronized) write() runs first and alone, and not until ‘testing.write()’ returns in this thread, the 2nd thread calling the (synchronized) read() will ever have a chance to run — this is the essence of ‘synchronized’ methods in the same object.

1.1.2.
Now, in your example, conceptually, ‘testing.write()’ will never return, in that for loop, it will ‘put’ the first 3 elements onto the queue and then kinda ‘spin wait’ for the 2nd thread to consume/’take’ some of these elements so it can ‘put’ more, but that will never happen due to aforementioned reason in 1.1.1

1.2 Programmatically

1.2.1.
(For producer) In ArrayBlockingQueue#put, the ‘spin wait’ I mentioned in 1.1.2 took form of

while (count == items.length) notFull.await();

1.2.2.
(For consumer) In ArrayBlockingQueue#take, it calls dequeue(), which in turn calls notFull.signal(), which will end the ‘spin wait’ in 1.2.1

2.Now, back to your original post’s title ‘What is the point of BlockingQueue not being able to work in synchronized Producer/Consumer methods?’.

2.1.
If I take the literal meaning of this question, then an answer could be ‘there are reasons for a convenient BlockingQueue facility to exist in JAVA other than using them in synchronized methods/blocks’, i.e. they can certainly live outside of any ‘synchronized’ structure and facilitate a vanilla producer/consumer implementation.

2.2.
However, if you meant to inquire one step further - Why can’t JAVA BlockQueue implementations work easily/nicely/smoothly in synchronized methods/blocks?

That will be a different question, a valid and interesting one that I am also incidentally puzzling about.

Specifically, see this post for further information (note that in this post, the consumer thread ‘hangs’ because of EMPTY queue and its possession of the exclusive lock, as opposed to your case where the producer thread ‘hangs’ because of FULL queue and its possession of the exclusive lock; but the core of the problems should be the same)

coder joe
  • 31
  • 3