In one of my Java 6 applications I have a thread that feeds the main thread with data, while also prefetching more records from a DB. It uses an ArrayBlockingQueue queue as a FIFO buffer and its main loop is something along these lines:
while (!Thread.interrupted()) {
if (source.hasNext()) {
try {
queue.put(source.next())
} catch (InterruptedException e) {
break;
}
} else {
break;
}
}
There is code that does a bit of clean-up after the loop terminates, such as poisoning the queue and releasing any resources, but this is pretty much all about it.
As it stands, there is no direct communication from the main thread to the feeder thread: the feeder thread is set-up with the proper options and then left on its own, using the blocking queue to control the data flow.
The problem appears when the main thread needs to shutdown the feeder when the queue is full. Since there is no direct control channel, the shutdown method uses the Thread interface to interrupt() the feeder thread. Unfortunately, in most cases the feeder thread remains blocked in put(), despite being interrupted – no exception is thrown.
From a brief perusal of the interrupt() documentation and the queue implementation source code, it seems to me that quite often put() blocks without using any of the interruptible facilities of the JVM. More specifically, on my current JVM (OpenJDK 1.6b22), it blocks on the sun.misc.Unsafe.park() native method. Perhaps it uses a spinlock or something else, but in any case, this seems to fall under the following case:
If none of the previous conditions hold then this thread’s interrupt status will be set.
A status flag is set, but the thread is still blocked in put() and does not iterate further so that the flag can be checked. The result? A zombie thread that just won’t die!
-
Is my understanding on this issue correct, or am I missing something?
-
What are the possible approaches to fix this issue? Right now I can only think of two solutions:
a. Calling
poll()a bunch of times on the queue to unblock the feeder thread: Ugly and not very reliable from what I’ve seen, but it mostly works.b. Use the
offer()method with a timeout instead ofput()to allow the thread to check its interrupt status within an acceptable time frame.
Unless I am missing something, this is a somewhat underdocumented caveat of the BlockingQueue implementations in Java. There seem to be some indications of it when the documentation e.g. suggests poisoning the queues to shutdown a worker thread, but I cannot find any explicit reference.
EDIT:
OK, there is a more, uh, drastic variation of solution (a) above: ArrayBlockingQueue.clear(). I think this should always work, even if it’s not exactly the definition of elegance…
I think there are two possible causes to your issue.
As described in The Law of the Sabotaged Doorbell you may not be handling the interrupt correctly. There you will find:
Either
source.hasNext()orsource.next()are consuming and discarding the interrupt status. See Added below for how I solved this problem.I am confident that interrupting a thread at
ArrayBlockingqueue.put()is an effective solution.Added
I solved problem 2 using a
CloseableBlockingQueuewhich can be closed from the reader end. In this way, once it is closed, allputcalls will shortcut. You can then check theclosedflag of the queue from the writer.You will also need the
Containerwhich is lock-free and O(1)put/get(although it is not strictly a collection). It uses aRingbehind the scenes.