Understanding Thread Interruption in Java
How to get a task running on a separate thread to finish early, or make that task responsive to such a finish request.
Join the DZone community and get the full member experience.
Join For FreeYou may come across use cases where you would need to perform some long running tasks on separate threads. You may have to request these tasks to finish doing their work, maybe even before the tasks are completely done, so that the threads they would be running on can be stopped. A few situations in which you may have to finish tasks early and stop threads are:
- While servicing a web request you may distribute the processing to multiple threads and some or all of the tasks fail to finish the processing within specified request processing time, or
- While shutting down an application that may use more than one thread to do some work, which may not have completed.
In this article I will try to answer the following questions:
- How to request a task, running on a separate thread, to finish early?
- How to make a task responsive to such a finish request?
Let’s try to answer the two questions by implementing an use case.
Example Use Case
The following are the requirements of the use case:
- Make a task that prints 0 through 9 on console.
- After printing a number the task should wait 1 sec before printing the next number.
- The task runs on a separate thread, other than main application thread.
- After starting the task the main application should wait for 3 sec and then shutdown.
- On shutdown the application should request the running task to finish.
- Before shutting down completely the application should, at the max, wait for 1 sec for the task to finish.
- The task should respond to the finish request by stopping immediately.
As per the requirements mentioned in the use case the task will take minimum 9 seconds to complete. Therefore after 3 seconds the main application will have to request the task to finish and if implemented correctly the task will not be able to print all the ten numbers from 0 through 9.
In this article, I will be focusing on the implementations of requirement 5 and requirement 7.
An Implementation of the Use Case Using Thread
The inline comment against a line of code mentions the use case requirement that has been met by the line.
public static void main(String[] args) throws InterruptedException {
Thread taskThread = new Thread(taskThatFinishesEarlyOnInterruption());
taskThread.start(); // requirement 3
Thread.sleep(3_000); // requirement 4
taskThread.interrupt(); // requirement 5
taskThread.join(1_000); // requirement 6
}
private static Runnable taskThatFinishesEarlyOnInterruption() {
return () -> {
for (int i = 0; i < 10; i++) {
System.out.print(i); // requirement 1
try {
Thread.sleep(1_000); // requirement 2
} catch (InterruptedException e) {
break; // requirement 7
}
}
};
}
Understanding the Implementation of Requirement 5
In the implementation of requirement 5, the main thread calls the taskThread
’s interrupt()
method. In Java, one thread cannot stop the other thread. A thread can only request the other thread to stop. The request is made in the form of an interruption. Calling the interrupt()
method on an instance of aThread
sets the interrupt status state as true
on the instance.
Use interruption to request a task, running on a separate thread, to finish.
Understanding the Implementation of Requirement 7
In the implementation of requirement 7, InterruptedException
is being handled by breaking out of the loop and thus finishing the task early.
Question arises that why did Thread.sleep() throw an InterruptedException? As soon as the taskThread
was interrupted by the main thread, the Thread.sleep(1_000)
responded to the interruption by throwing the exception. In fact almost all blocking methods respond to interruption by throwing InterruptedException.The decision of what to do in the case of interruption is left to the implementing code, which in this example is breaking out of the for loop as per the requirement in the use case.
Note: Calls to sleep() and join() methods in main() method are blocking and may also throw InterruptedException upon interruption. Handling of the exception here has been omitted for brevity.
Handle interruption request, which in most cases is done by handling InterruptedException, in the task to make it responsive to a finish request.
An Implementation of the Use Case Using the Executor
The same use case can be implemented using Executor
framework provided by Java and can be found under the java.util.concurrent
package. Usage of the Executor framework is preferred over Threads as it provides separation of task execution from the thread management. In the implementation below the task is submitted to ExecutorService
, a sub-interface of Executor, using the submit()
method. The service runs the task on the thread it holds. The service’s shutdownNow()
method interrupts the currently running task andawaitTermination()
method waits for the service to shutdown.
public static void main(String[] args) throws InterruptedException {
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.submit(taskThatFinishesEarlyOnInterruption()); // requirement 3
Thread.sleep(3_000); // requirement 4
executor.shutdownNow(); // requirement 5
executor.awaitTermination(1, TimeUnit.SECONDS); // requirement 6
}
// implementation of taskThatFinishesEarlyOnInterruption() remains the same
When using the Executor framework, you can interrupt a specific task without shutting down theExecutorService
. On submitting a task to the service an instance of Future<?>
is returned by the service. You may call the cancel()
method on that instance to interrupt the task. In situations when you service a web request by running parallel tasks, this method of cancelling tasks and not shutting down the service helps in re-using the service across multiple requests. In such situations you may want to shutdown the service only on shutdown of your web application. Calling the cancel()
with true
causes the task to be interrupted.
Future<?> submittedTask = executor.submit(someTask());
...
submittedTask.cancel(true) // if conditions to cancel the task have been met
The Executor framework is a complete asynchronous task execution framework. If you have not explored it yet, I request to you to read about it. It will be a great addition to your development toolbox.
InterruptedException and Interruption Status
Before I finish, I wanted to emphasize on an important detail about what happens to a thread’s interruption status when a blocking code responds to interruption by throwing InterruptedException
. I had left out the detail till now to avoid confusion.
Before a blocking code throws an InterruptedException, it marks the interruption status as false. Thus, when handling of the InterruptedException is done, you should also preserve the interruption status by calling
Thread.currentThread().interrupt()
.
Let’s see how this information applies to the example below. In the task that is submitted to the ExecutorService, the printNumbers()
method is called twice. When the task is interrupted by a call toshutdownNow()
, the first call to the method finishes early and then the execution reaches the second call. The interruption is called by the main thread only once. The interruption is communicated to the second execution of the printNumber()
method by the call to Thread.currentThread().interrupt()
during the first execution. Hence the second execution also finishes early just after printing the first number. Not preserving the interruption status would have caused the second execution of the method to run fully for 9 seconds.
public static void main(String[] args) throws InterruptedException {
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<?> future = executor.submit(() -> {
printNumbers(); // first call
printNumbers(); // second call
});
Thread.sleep(3_000);
executor.shutdownNow(); // will interrupt the task
executor.awaitTermination(3, TimeUnit.SECONDS);
}
private static void printNumbers() {
for (int i = 0; i < 10; i++) {
System.out.print(i);
try {
Thread.sleep(1_000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // preserve interruption status
break;
}
}
}
Summary
The answers to the two questions that I had set out to answer are:
- How to request a task, running on a separate thread, to finish early? - Use interruption.
- If using Thread directly in your code, you may call
interrupt()
on the instance of thread. - If using Executor framework, you may cancel each task by calling
cancel()
onFuture
- If using Executor framework, you may shutdown the
ExecutorService
by calling theshutdownNow()
method.
- If using Thread directly in your code, you may call
- How to make a task responsive to such a finish request? - Handle interruption request, which in most of the cases is done by handling
InterruptedException
. Also preserve the interruption status by callingThread.currentThread().interrupt()
.
Published at DZone with permission of Praveer Gupta. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments