14

I know this question has been asked and answered many times before, but I just couldn't figure out a trick on the examples found around internet, like this or that one.

Both of these solutions check for emptiness of the blocking queue's array/queue/linkedlist to notifyAll waiting threads in put() method and vice versa in get() methods. A comment in the second link emphasizes this situation and mentions that that's not necessary.

So the question is; It also seems a bit odd to me to check whether the queue is empty | full to notify all waiting threads. Any ideas?

Thanks in advance.

Community
  • 1
  • 1
tugcem
  • 1,078
  • 3
  • 14
  • 25
  • 2
    Part of the issue relates to the difficulty of using `wait` and `notify` correctly in concurrent programs; to quote Joshua Bloch, they are like "the low-level (assembly language) of concurrent programming." He advocates using `notifyAll` and that waiting threads should always do a check within a loop when notified so that they can keep waiting as needed. Really, you should simply not use wait/notify at all and instead always plan to use the higher level concurrent APIs provided in Java SE 5. Rather than threads and wait/notify, design concurrent apps in terms of tasks and executors. – scottb Nov 21 '13 at 00:31
  • yep, I'm completely agree with you, oldies but goodies: no need to re-invent the wheel. But just think that we need to use wait and notifyAll for this case. I know that wait() should be used within a loop, all explained in javadocs (spurious wakeups) : http://docs.oracle.com/javase/6/docs/api/java/lang/Object.html#wait(long). But what the hack about that conditions for notifying? That's the point I couldn't get. – tugcem Nov 21 '13 at 00:44
  • See [here](http://fuseyism.com/classpath/doc/java/util/concurrent/LinkedBlockingQueue-source.html) - they use `ReentrantLock` and `Condition`s,not `wait`/`notify` at all. – OldCurmudgeon Nov 21 '13 at 00:46
  • we're moving out of the scope of problem! I do look for the actual implementations, but that's probably the most naive way for a self-implemented blocking queue, and why there are same 'if' conditions in different solutions? Just a copy paste? Unconscious coding? :) – tugcem Nov 21 '13 at 00:55
  • I was reading through jenkov's tutorials, and from my understanding, the `if (this.queue.size() == this.limit) { notifyAll(); }` is added in `deque` because, the threads that were enqueuing goes to wait() only when size hits the limit. When one of the threads deques the element when size is full, it is supposed to notify the waiting threads. Correct me if I'm wrong :) – Crocode May 16 '16 at 10:12

4 Answers4

21

I know this is an old question by now, but after reading the question and answers I couldn't help my self, I hope you find this useful.

Regarding checking if the queue is actually full or empty before notifying other waiting threads, you're missing something which is both methods put (T t) and T get() are both synchronized methods, meaning that only one thread can enter one of these methods at a time, yet this will not prevent them from working together, so if a thread-a has entered put (T t) method another thread-b can still enter and start executing the instructions in T get() method before thread-a has exited put (T t), and so this double-checking design is will make the developer feel a little bit more safe because you can't know if future cpu context switching if will or when will happen.

A better and a more recommended approach is to use Reentrant Locks and Conditions:

//I've edited the source code from this link

Condition isFullCondition;
Condition isEmptyCondition;
Lock lock;

public BQueue() {
    this(Integer.MAX_VALUE);
}

public BQueue(int limit) {
    this.limit = limit;
    lock = new ReentrantLock();
    isFullCondition = lock.newCondition();
    isEmptyCondition = lock.newCondition();
}

public void put (T t) {
    lock.lock();
    try {
       while (isFull()) {
            try {
                isFullCondition.await();
            } catch (InterruptedException ex) {}
        }
        q.add(t);
        isEmptyCondition.signalAll();
    } finally {
        lock.unlock();
    }
 }

public T get() {
    T t = null;
    lock.lock();
    try {
        while (isEmpty()) {
            try {
                isEmptyCondition.await();
            } catch (InterruptedException ex) {}
        }
        t = q.poll();
        isFullCondition.signalAll();
    } finally { 
        lock.unlock();
    }
    return t;
}

Using this approach there's no need for double checking, because the lock object is shared between the two methods, meaning only one thread a or b can enter any of these methods at a time unlike synchronized methods which creates different monitors, and only those threads waiting because the queue is full will be notified when there's more space, and the same goes for threads waiting because the queue is empty, this will lead to a better cpu utilization. you can find more detailed example with source code here

Community
  • 1
  • 1
Ayesh Qumhieh
  • 1,117
  • 2
  • 11
  • 26
  • 2
    instead of isfullConditon , "isNotfullCondtion" will make it more readable +1 for the implementation – jayendra bhatt Jan 19 '17 at 03:49
  • Could you please clarify the statement - "if a thread-a has entered put (T t) method another thread-b can still enter and start executing the instructions in T get() method before thread-a has exited put (T t), " In the above context i guess both methods lock the same monitor (the "intrinsic lock" ) .Therefore, how can we simultaneously execute them on the same object from different threads? – Sunny Jan 08 '22 at 16:19
  • @Sunny , It's kind of hard to remember, this answer was in 2014 :) But what I do remember in my answer is that I was trying to tell the difference between using Java's synchronized methods vs reentrant locks. – Ayesh Qumhieh Jan 09 '22 at 20:56
1

I think logically there is no harm doing that extra check before notifyAll().

You can simply notifyAll() once you put/get something from the queue. Everything will still work, and your code is shorter. However, there is also no harm checking if anyone is potentially waiting (by checking if hitting the boundary of queue) before you invoke notifyAll(). This extra piece of logic saves unnecessary notifyAll() invocations.

It just depends on you want a shorter and cleaner code, or you want your code to run more efficiently. (Haven't looked into notifyAll() 's implementation. If it is a cheap operation if there is no-one waiting, the performance gain may not be obvious for that extra checking anyway)

Adrian Shum
  • 38,812
  • 10
  • 83
  • 131
1

The reason why the authors used notifyAll() is simple: they had no clue whether or not it was necessary, so they decided for the "safer" option.

In the above example it would be sufficient to just call notify() as for each single element added, only a single thread waiting can be served under all circumstances.

This becomes more obvious, if your queue as well has the option to add multiple elements in one step like addAll(Collection<T> list), as in this case more than one thread waiting on an empty list could be served, to be exact: as many threads as elements have been added.

The notifyAll() however causes an extra overhead in the special single-element case, as many threads are woken up unnecessarily and therefore have to be put to sleep again, blocking queue access in the meantime. So replacing notifyAll() with notify() would improve speed in this special case.

But then not using wait/notify and synchronized at all, but instead use the concurrent package would increase speed by a lot more than any smart wait/notify implementation could ever get to.

TwoThe
  • 13,879
  • 6
  • 30
  • 54
0

I would like to write a simple blocking queue implementation which will help the people to understand this easily. This is for someone who is novice to this.

class BlockingQueue {
private List queue = new LinkedList();

private int limit = 10;

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

public synchronized void enqueue(Object ele) throws InterruptedException {
    while(queue.size() == limit)
        wait();
    if(queue.size() == 0)
        notifyAll();
    // add
    queue.add(ele);
}

public synchronized Object deque() throws InterruptedException {
    while (queue.size() == 0)
        wait();
    if(queue.size() == limit)
        notifyAll();
    return queue.remove(0);
}

}


Sumanth Varada
  • 1,122
  • 1
  • 16
  • 17
  • Could you please help to understand the logic for notifying only when queue is empty and not as the elements are added ? Also same with deque, notifying only if queue is full ? – Sunny Jan 08 '22 at 16:25