User1291
User1291

Reputation: 8209

Java - concurrency: LinkedBlockingQueue, "try again if failed"

int capacity = ...
BlockingQueue q = new LinkedBlockingQueue<Element>(capacity);

Now, I do feel mildly ridiculous asking this but I'm not particularly savvy when it comes to java concurrency, so I would appreciate some help with choosing the correct way to enqueue something (and dequeue, actually, but I expect when we cleared up the one, the other will fall into place by itself).

There is, of course

while(!q.offer(e));

But I'm a bit wary of spinning implementations in a multi-threaded environment.

And I can't do

synchronized(q){
    while(!q.offer(e))q.wait();
}

either because the wakeup calls will go to internal (private) instances of Condition, meaning this would be a suicide-by-sleeping-pills implementation.

However, I'm also not particularly fond of

try{
    q.put(e);
}catch(InterruptedException ex){}

(even though it does seem to be a popular choice in online examples) because although this would do the waiting for me, I know of no reliable way to detect when an exception would force me to try again. I could do something like

boolean success = false;
do{
    try{
        q.put(e);
        success = true;
    }catch(InterruptedException ex){}
}while(!success)

But then I'd end up enqueuing the same element multiple times if the exception takes place in-between the put and the assignment to success.

I could do

boolean success = true;
do{
    try{
        q.put(e);
    }catch(InterruptedException ex){
        success = false;
    }
}while(!success)

But I remember having read (way back) that you shouldn't rely on exception handling for conditionals (though I can't seem to remember the reason why this is discouraged).

So ... what options do I have? Do I need to spin or is there something more intelligent?

Upvotes: 2

Views: 666

Answers (3)

Kayaman
Kayaman

Reputation: 73568

The put() implementation would be the correct one, blocking until interrupted or successful. The offer() is a bad idea if all you are doing is spinning (see first comment for disclaimer).

As Nicolas explained, the handling of the InterruptedException is not straightforward and depends a lot on what your other code is doing, but your concern with "if the exception takes place in-between the put and the assignment to success", that can never happen: a blocking call (like put()) can throw that exception, but it cannot occur between put() and the assignment, or at the assignment.

Lastly, there's no need to synchronize on anything. The main idea in many of java.util.concurrent classes is to avoid or abstract away the explicit synchronization.

Upvotes: 1

Nicolas Filotto
Nicolas Filotto

Reputation: 45005

It is not a good practice to catch an InterruptedException as you do since your code won't be responsive to interruption anymore. An InterruptedException is usually thrown by methods that are responsive to interruptions (current Thread being interrupted) such as the methods of type await, wait, join, sleep and many others, this should not be considered as a failure but rather as it really is, a Thread's status change that needs be taken into consideration.

As Brian Goetz explains in Java Concurrency in Practice, I quote:

When your code calls a method that throws InterruptedException, then your method is a blocking method too, and must have a plan for responding to interruption. For library code, there are basically two choices:

Propagate the InterruptedException. This is often the most sensible policy if you can get away with it just propagate the InterruptedException to your caller. This could involve not catching InterruptedException, or catching it and throwing it again after performing some brief activity-specific cleanup.

Restore the interrupt. Sometimes you cannot throw InterruptedException, for instance when your code is part of a Runnable. In these situations, you must catch InterruptedException and restore the interrupted status by calling interrupt on the current thread, so that code higher up the call stack can see that an interrupt was issued.

So in your case, you should simply use put(E) as it will make the calling thread waits for space to become available if needed and propagate the InterruptedException in order to keep on being responsive to interruptions.


But then I'd end up enqueuing the same element multiple times if the exception takes place in-between the put and the assignment to success.

This can simply never happen since an assignment of a boolean will never throw any exceptions (except a NPE in case of an un-boxing). And only methods responsive to interruption can throw such kind of exceptions as explained above which is clearly not the case of an assignment.

Upvotes: 1

Joe C
Joe C

Reputation: 15714

So a few points from the LinkedBlockingQueue Javadoc:

  • The put method will only throw exceptions in two circumstances:
    • The thread is interrupted, in which case you should stop whatever you're doing anyway. See this question for more about InterruptedExceptions.
    • The element you're inserting is null, which is another bug entirely.

So overall, you can just use put to wait for space to become available. If either of these particular exceptions is thrown, then you shouldn't retry anyway.

Upvotes: 0

Related Questions