The other Thread did not join() your Thread in case of an InterruptedException. join() is supposed to wait forever or until the calling Thread is interrupt()-ed.
First you need to consider whether or not an InterruptedException can actually happen. There are several possibilities how to deal with it. You can:
- Swallow it. Warning Only do so if you're sure that
InterruptedException will never happen. (Are you sure? Really?)
- Set the interrupted flag again. This is what you do if your code doesn't know how to handle it but the Thread is doing other stuff as well that might be interested in the
InterruptedException.
- Propagate it. This is the best solution if your current method doesn't know what to do with it, but maybe the caller knows.
- Handle it. Maybe your current
Thread simply wants to stop (by completing its run() method) when you received the InterruptedException.
- Wrap it in another exception. There are two variants of this.
- You may want to wrap it in an Assertion if the
InterruptedException shouldn't have happened in the first place and clearly is an error.
- You may want to wrap it in another exception if your method is bound to a specific contract that doesn't allow
InterruptedException.
In any case, I strongly recommend to log() the InterruptedException in some way. If you don't log it, and it happens, the program's behavior might be difficult to understand without that log information about the InterruptedException.
Swallowing it
Warning Only swallow it if you are absolutely sure that this is okay in your case. You have been warned.
public static void joinThread(final Thread thread) {
while (true)
try {
thread.join();
return;
} catch (final InterruptedException e) {
Logger.getGlobal().log(Level.ERROR, "Unexpected InterruptedException", e);
}
}
As a variant of this, you can simply ignore it without a retry.
public static void joinThread(final Thread thread) {
try {
thread.join();
} catch (final InterruptedException e) {
Logger.getGlobal().log(Level.ERROR, "Unexpected InterruptedException", e);
}
}
Setting the interrupted flag again.
This is what you do if the Thread is doing other stuff as well, your code doesn't know how to deal with the InterruptedException but the other code does.
public static void joinThread(final Thread thread) {
try {
thread.join();
} catch (final InterruptedException e) {
Logger.getGlobal().log(Level.INFO, "Unexpected InterruptedException, resetting flag", e);
Thread.currentThread().interrupt();
}
}
But be warned! This only is a good idea if other code really knows what to do. If the other code does the same thing, and the stuff is in a loop (which is often the case for Threads), you've just turned your nice blocking code into busy-waiting garbage because the interrupted flag never gets cleared properly. While this solution is advertised as the best or true solution in many blog articles, it's nonsense as long as there isn't any code that knows how to deal with InterruptedException and actually keeps the flag cleared instead of resetting it.
Propagating it
This is the best solution in case the method itself doesn't know how to handle it, but the caller might know.
public static void joinThread(final Thread thread) throws InterruptedException {
try {
thread.join();
} catch (final InterruptedException e) {
Logger.getGlobal().log(Level.FINE, "Got InterruptedException, propagating it to the caller", e);
throw e;
}
}
In case the caller does something nice with it, you can remove the unnecessary parts like logging and re-throwing.
Handling it.
In case the method itself knows that when interrupted, it has to do something, it can simply catch the exception and do whatever is expected.
public static void joinThread(final Thread thread) {
try {
thread.join();
} catch (final InterruptedException e) {
Logger.getGlobal().log(Level.FINE, "Got InterruptedException, handling it", e);
// ...whatever...
}
}
Wrapping it.
If the exception was not supposed to happen, because the application does not support interruption, and Thread.interrupt() or ThreadGroup.interrupt() shouldn't have been called in the first place, turning the exception into an AssertionError is the way to go.
There are two possibilities to do so.
// Only throws AssertionError if assertions are enabled.
public static void joinThread(final Thread thread) {
try {
thread.join();
} catch (final InterruptedException e) {
Logger.getGlobal().log(Level.ERROR, "Unexpected InterruptedException", e);
assert false : e;
}
}
// Always throws AssertionError.
public static void joinThread(final Thread thread) {
try {
thread.join();
} catch (final InterruptedException e) {
Logger.getGlobal().log(Level.FATAL, "Unexpected InterruptedException", e);
throw new AssertionError(e, "Unexpected Thread interruption.");
}
}
If the InterruptedException is to be propagated but the caller doesn't support the exception and cannot be changed, you can wrap it into an exception that is supported. Whether or not this works and is a good idea really depends much on the caller.
public static void joinThread(final Thread thread) {
try {
thread.join();
} catch (final InterruptedException e) {
Logger.getGlobal().log(Level.ERROR, "Unexpected InterruptedException", e);
throw new SomeException(e, "Unexpected Thread interruption.");
}
}
Final notes
There has been some discussion that swallowing is always bad. That's not true. If you have 100 lines of code and do some Thread.sleep() somewhere for some good reason, swallowing is usually fine. As always, it depends. In the end, the semantics of Thread.interrupt() are not so different from SIGHUP, and SIGHUP is usually ignored.
There also has been some discussion whether turning it into an AssertionError is a good idea. AssertionError is an unchecked Throwable, not even an Exception, which means the compiler doesn't force code to deal with it and most code isn't written to deal with it - that's intentional, because by using AssertionError we say here's an error that was so unexpected that the code doesn't know how to deal with it and therefore wants to stop. Whether AssertionError terminates the VM depends on which Thread(s) would be taken down due to the AssertionError being thrown up to the UncaughtExceptionHandlers of the affected Threads.