6 minutes read

A thread terminates when its run function stops. Sometimes, however, you may need to terminate a task that is currently running: for example, when shutting down an application with multiple threads. Kotlin provides a mechanism called interruption for requesting a thread to stop. Interruption does not force a thread to react immediately but notifies it about such a demand.

interrupt() and isInterrupted

We cannot interrupt a thread by just sending a special signal. The interrupt mechanism is much more complex and is based on a hidden variable or the thread's "interrupted" flag.

Invoking the interrupt() function on an instance of the Thread class sets its "interrupted" flag as true:

fun main() {
    val customThread: Thread = CustomThread()
    customThread.start()
    println(Thread.currentThread().id)
    customThread.interrupt() // interrupt this thread
}
class CustomThread: Thread() {
    override fun run() {
        while (!isInterrupted) {
            try {
                sleep(1000) // it may throw InterruptedException
            } catch (e: InterruptedException) {
                println("sleeping was interrupted")
                break // stop the loop
            }
        }
        print("${currentThread().id} finished")
    }
}

The reaction to this event depends on the interrupted thread itself. In the most common case, it stops the execution. However, the thread may simply ignore the flag.

Depending on the current state of the thread, interruptions are handled differently. Invoking customThread.interrupt() will cause InterruptedException if the thread is sleeping or joining another thread. If the thread is running, calling the .interrupt() method will change the isInterrupted flag to true. This flag allows you to monitor whether the flow is interrupted.

Here is an example of how a thread may handle an interruption:

class CustomThread: Thread() {
    override fun run() {
        while (!isInterrupted) {
            try {
                doAction()
                sleep(1000) // it may throw InterruptedException
            } catch (e: InterruptedException) {
                println("sleeping was interrupted")
                break // stop the loop
            }
        }
        print("$name finished")
    }

    private fun doAction() {
        // something useful
    }
}

When this thread is running, an interruption may occur on any statement inside the run function: when checking the loop's condition, performing doAction, or during sleep. If the thread is sleeping, sleep(1000) throws an InterruptedException, which is handled. In other cases, the loop is stopped according to the condition in the next iteration.

If you prefer implementing Runnable rather than extending Thread directly, you may use the function Thread.interrupted() inside the run function. The main difference between the Thread.interrupted() method and the previously discussed isInterrupted flag is that the Thread.interrupted() method resets the interrupt state to false.

In the following paragraph, you will see an example of using this method.

An example: counting with interruption

As an example, we will consider a task that counts numbers while the thread is not interrupted.

class CountingTask: Runnable {
    override fun run() {
        println("Start counting")
        var i = 1 // the first number to print
        try {
            while (!Thread.interrupted()) {
                println(i)
                i++
                Thread.sleep(1000)
            }
        } catch (e: InterruptedException) {
            println("Sleeping was interrupted")
        }
        println("Finishing")
    }
}

In this implementation, the thread is sleeping most of the time, and interruption will often occur during the sleep state. If the program does not print the string "Sleeping was interrupted", it means that the thread was interrupted during work, not sleep.

In the main function, we create a thread to perform the task and then interrupt the thread:

fun main() {
    val counter: Thread = Thread(CountingTask())
    counter.start()
    sleep(3500L)
    counter.interrupt() // interrupt this thread
    counter.join()
}

Note that in the main function, both functions sleep and join may also throw InterruptedException upon being interrupted. For brevity's sake, we're not mentioning the exception handling.

The program output is:

Start counting
1
2
3
4
Sleeping was interrupted
Finishing

Conclusion

In this topic, we've learned how we can terminate the thread that is being executed in code.

Now, you can practice your knowledge, but be careful with interrupting tasks and remember about InterruptedException, which occurs when the thread is sleeping or joining another thread.

20 learners liked this piece of theory. 0 didn't like it. What about you?
Report a typo