I am reading Java Concurrency in Practice and encounter the following code snippet (Listing 7.15. Adding reliable cancellation to LogWriter.).
public class LogService {
private final BlockingQueue<String> queue;
private final LoggerThread loggerThread;
private final PrintWriter writer;
@GuardedBy("this") private boolean isShutdown;
@GuardedBy("this") private int reservations;
public void start() { loggerThread.start(); }
public void stop() {
synchronized (this) { isShutdown = true; }
loggerThread.interrupt();
}
public void log(String msg) throws InterruptedException {
synchronized (this) {
if (isShutdown)
throw new IllegalStateException(...);
++reservations;
}
queue.put(msg);
}
private class LoggerThread extends Thread {
public void run() {
try {
while (true) {
try {
synchronized (LogService.this) {
if (isShutdown && reservations == 0)
break;
}
String msg = queue.take();
synchronized (LogService.this) {
--reservations;
}
writer.println(msg);
} catch (InterruptedException e) { /* retry */ } // interruption policy
}
} finally {
writer.close();
}
}
}
}
LogService is used to implement "multiple-log-producer, single-log-consumer" (multiple threads can execute log(String msg) task in order to put log into queue, one loggerThread can consume the log in queue).
But the LoggerThread has defined its own interruption policy, which is "do nothing" in the catch block. So, what's the point of calling loggerThread.interrupt();?
If we look at the loop:
while (true) {
try {
synchronized (LogService.this) {
if (isShutdown && reservations == 0)
break;
}
String msg = queue.take();
synchronized (LogService.this) {
--reservations;
}
writer.println(msg);
} catch (InterruptedException e) { /* retry */ } // interruption policy
}
We see it has the following behavior:
queue.take(), waiting for incoming messages.LogService has not been shutdown or there are still messages to log.LogService having been shutdown then the thread continues on as if nothing changed. This prevents erroneous interrupts from breaking the service.There's nothing to do in the catch block as the code to break out of the loop is handled elsewhere in the loop. If you wanted, you could have:
catch (InterruptedException ex) {
synchronized (LogService.this) {
if (isShutdown && reservations == 0) break;
}
}
But that would be duplicated, redundant code for no reason. We also don't want an unconditional break in the catch block because we want to loop until all messages have been logged, even after a shutdown; again, you could put that logic in the catch block but why do that when the rest of the loop already does the exact same thing.
And we need the call to loggerThread.interrupt() because the thread might be blocked in the queue.take() call. The interrupt wakes up the thread allowing it to check the break-out-of-loop condition. Without the interrupt the thread could remain blocked and never die.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With