2

I've tried reading some answers to similar questions here (I always do that) but did not find (or did not understand?) the answer to this particular issue.

I am implementing a fairly simple consumer-producer class, which receives elements to a list from a different thread and consumes them repeatedly. The class has the following code:

public class ProduceConsume implements Runnable
{

    LinkedList<Integer> _list = new LinkedList<Integer>();

    public synchronized void produce(Integer i)
    {
        _list.add(i);
        notify();
    }

    public void run()
    {
        while(true)
        {
            Integer i = consume();

            // Do something with the integer...
        }
    }

    private synchronized Integer consume()
    {
        if(_list.size() == 0)
        {
            try
            {
                wait();
            }
            catch(InterruptedException e){}

            return _list.poll();
        }
    }
}

The problem is - it usually works fine, but sometimes, the execution gets to

return _list.poll();

with the list still empty. I can't wrap my head around it - am I doing something terribly wrong? Shouldn't the runnable thread, which repeatedly tries to poll detect a zero length list, wait, and be awakened only after the producer method is done, hence making the list non-empty?

Nothing else "touches" the class from the outside, except for calls to produce. No other threads are synchronized on the runnable class.

By the way, for several reasons, I wish to use my own variant and not classes such as CopyOnWriteArrayList, etc.

Thanks! Any help would be greatly appreciated.

P.S - I have not used the wait-notify many times, but when I did, in the past, it worked. So if I apologize if I made some huge stupid error!

Nathan Hughes
  • 94,330
  • 19
  • 181
  • 276
amirkr
  • 173
  • 3
  • 15
  • 1
    Show us how you use the `ProduceConsume` instance. – Sotirios Delimanolis Jul 28 '15 at 17:24
  • Thanks for your reply - The program is pretty complicated outside of the ProduceConsume class but it is a straightforward use - I can reduce it to, say, another thread (the main thread for example), which has an infinite loop that pauses for N miliseconds at each iteration and sends a random number to ProduceConsume. – amirkr Jul 28 '15 at 17:29
  • Thanks RealSkeptics. I would change to a loop and see if the problem persists (it's tricky to debug... need to wait an hour or so before it happens). The thing is it is really weird if the thread is "just" woken up for no reason - since no other thread (at least not my threads) can possibly wake this ProduceConsume up. Thanks again. – amirkr Jul 28 '15 at 17:33
  • 1
    Read all upvoted answers to that question. The condition is called "spurious wakeups" and is quite well-known. – RealSkeptic Jul 28 '15 at 17:34
  • I will. thank you again. – amirkr Jul 28 '15 at 17:36
  • 1
    I assume you can't just use a BlockingQueue? – Peter Lawrey Jul 28 '15 at 17:37
  • I actually could... did not know this class. Feel so newbish (and rightfully so). Anyways I will mark this question as answered, and duplicated (I really tried googling and searching it...). Thank you all again it was very very helpful. – amirkr Jul 28 '15 at 17:43
  • 1
    _Feel so newbish_, and may you _always_ feel that way. That is to say, may you never stop learning. The day you stop learning in this business will be the beginning of the end of your career. I've been developing software for more than 35 years now, and I _still_ feel newbish. – Solomon Slow Jul 28 '15 at 17:56
  • @jameslarge Thanks James :) – amirkr Jul 28 '15 at 17:57
  • Just wanted to let you guys know the while loop thing did the trick. It was probably indeed a spurious wake up that crashed the code from time to time. Thank you all again. – amirkr Jul 29 '15 at 12:31

2 Answers2

2

Since wait releases the lock you can't reason based on conditions tested before it started waiting, assuming the condition must have changed once wait is exited is not valid. You need to call wait in a loop, so that once the thread ceases waiting and takes the lock again, it checks that the condition it's waiting for has the expected value:

private synchronized Integer consume()
{
    try {
        while (_list.size() == 0) {
            wait();
        }            
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
    return _list.poll();
}

From the Oracle tutorial:

Note: Always invoke wait inside a loop that tests for the condition being waited for.

Also it's not safe to assume that just because wait returned that something sent a notification. wait can return even if there is no notification (the spurious wakeup).

It's hard to say what caused what you're seeing without a complete working example.

The linked Oracle tutorial page has a Producer Consumer example you might want to look at.

Nathan Hughes
  • 94,330
  • 19
  • 181
  • 276
  • Thank you. I will change the conditional to a while loop. The thing is, as I have written above, that it is really weird the thread is woken up without the list being filled. No other thread (not one of mine's) can wake ProduceConsume up from it's wait(), so theoretically it is a bit bizzare. But hey, if it works... – amirkr Jul 28 '15 at 17:34
  • 1
    @amirkr the problem is that the underlying system call doesn't guarantee it won't be woken when no notify() was called. – Peter Lawrey Jul 28 '15 at 17:36
  • As I said it is weird that the coditional changes, since the list was empty - the producer inserted a number to the list - it is now not empty (nothing happens in the eanwhile, not by my code) - the thread awakens and proceeds. In 99.99% of the cases it works. But sometimes, it doesn't. Hopefully the loop thing will change it (I still find it weird but again, if it works then... fine by me :)) – amirkr Jul 28 '15 at 17:39
  • @PeterLawrey - that could explain it! Thanks! – amirkr Jul 28 '15 at 17:40
  • 1
    @amirkr: without a complete code example it's hard to say exactly. definitely the while loop should help since it checks what the object's state is. – Nathan Hughes Jul 28 '15 at 17:42
  • Just wanted to let you guys know the while loop thing did the trick. It was probably indeed a spurious wake up that crashed the code from time to time. Thank you all again. – amirkr Jul 29 '15 at 12:31
  • @amirkr: yw. the wakeup thing apparently is caused by a race condition. i'm hesitant to attribute errors to it right off because it makes me lazy, if there is another cause i'll miss it. but it can happen. – Nathan Hughes Jul 29 '15 at 12:58
2

As the Javadoc for Object.wait states

As in the one argument version, interrupts and spurious wakeups are possible, and this method should always be used in a loop:

 synchronized (obj) {
     while (<condition does not hold>)
         obj.wait();
     ... // Perform action appropriate to condition
 }

Additionally, you shouldn't ignore an exception like InterruptedException. This will look like a spurious wake up and as you say produces an error.

private synchronized Integer consume() {
    try {
        while (_list.isEmpty()) 
            wait();
        return _list.poll();
    } catch(InterruptedException e) {
        throw new IllegalStateException("Interrupted");
    }
}
Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
  • So you think this is a spurious wakeup? – Sotirios Delimanolis Jul 28 '15 at 17:32
  • @SotiriosDelimanolis that is why I wrote this answer if that is what you mean. IMHO It is the most likely explanation of occasionally waking up even though nothing was there. – Peter Lawrey Jul 28 '15 at 17:35
  • Thank you for your reply. I just forgot to write the exception handling in the InterruptedException - if that happens, my code prints e.printStackTrace(). I just forgot to write it here. No stack traces areprinted :-) – amirkr Jul 28 '15 at 17:37
  • @amirkr as you say, you can't call list.poll() unless something was actually added. – Peter Lawrey Jul 28 '15 at 17:38