I made a thread pool based on the example on this page. In the worker thread we have the infinite loop that never lets the thread die and the wait() method call that pauses the thread when there is no work to do:
while (true) {
synchronized(queue) {
loop:while (queue.isEmpty()) { // labled the loop so we can return here
try
{
queue.wait();
if(queue.isEmpty()) // check the condition predicate again
continue loop;
}
catch (InterruptedException ignored)
{
}
}
r = (Runnable) queue.removeFirst();
}
// If we don't catch RuntimeException,
// the pool could leak threads
try {
r.run();
}
catch (RuntimeException e) {
// You might want to log something here
}
The fact is that r = (Runnable) queue.removeFirst();
can throw a NoSuchElementException
which is a RuntimeException if the queue is empty. And when such an exception is thrown on that line, the current thread holding the mutex dies and the pool leaks the thread. The mutex seems to be released when the thread dies.
However, if you instead of using the default synchronized
keyword to synchronize the queue, use the ReentrantLock
to lock and Condition
for signaling and awaiting, the current thread that holds the mutex does not seem to release the lock when it interrupts unexpectedly.
So, in my case, when I checked with JVisualVM under the Threads tab I could see that the AWT-EventQueue-0
thread was wating for Thread-1
to release the mutex. But the Thread-1 died upon its way to run the task and was unexpectedly terminated (RuntumeException
) and the mutex did not seem to be released.
My questions:
1) Does not ReentrantLocks being released if the thread that holds it terminates unexpectedly?
2) Is there any difference between while (queue.isEmpty()) {
and if (queue.isEmpty()) {
in the code snippet above? I cannot see any difference since the thread will wait in both cases. But I think the behaviour it different when using if
(like if more than one thread can affect the queue).
EDIT
Java Concurrency in Practice states:
For all these reasons, when you wake up from wait you must test the condition predicate again, and go back to waiting (or fail) if it is not yet true. Since you can wake up repeatedly without your condition predicate being true, you must therefore always call wait from within a loop, testing thecondition predicate in each iteration.
Look at my edit in the code above, now the code should be correctly as stated in Java Concurrency in Practice.