Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Understanding thread interruption in Java

I was reading thread interrupting from this article. It says following:

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 callingThread.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;
        }
    }
}

I tried running above code and it prints:

01230

When I comment Thread.currentThread().interrupt(); from catch block, it prints:

01230123456789

Though I feel I understand explanation before code, I dont think I understand why the explanation is correct, partly because I did not find any related explanation / sentence in the official doc. Here are my specific doubts:

  1. Does that mean we need every method call in Runnable submitted to ExecutorService.submit() to have catch InterruptedException and call Thread.currentThread().interrupt() in it for whole Runnable body to interrpt? If we miss to catch InterruptedException and call Thread.currentThread().interrupt() in any method call in Runnable, then it will not be interrupted?

  2. If above is correct, why there is not such explanation in the doc of Thread.interrupt()?

  3. Is it like if we want to do any closure task on interruption in any method, then only we should catch InterruptedException, perform any task to be done and then call Thread.currentThread().interrupt()?

  4. If answer to question 3 is yes, then when we should be doing closure task? I feel any closure task should be done before Thread.currentThread().interrupt(), but in the given example, break is called after Thread.currentThread().interrupt().

  5. After thinking on question 4 a bit more, I feel I dont understand it clearly how thread interruption is handled. Earlier I felt interrupted thread simply gets killed immediately, but that does not seem to be the case. How thread interruption occurs? Is there any oracle official link explaining the same?

like image 292
anir Avatar asked Sep 04 '25 17:09

anir


1 Answers

The biggest misconception with thread interruption is that this works out of the box. Yes, the basic mechanics to set the interrupt flag of a thread are made available to deal with thread interruption, but how a thread should do this is still up to the developer. See the note on ExecutorService shutdownNow method:

There are no guarantees beyond best-effort attempts to stop processing actively executing tasks. For example, typical implementations will cancel via Thread.interrupt, so any task that fails to respond to interrupts may never terminate.

For example:

public class Hello {
  public static void main(String[] args) throws InterruptedException {
    ExecutorService executor = Executors.newSingleThreadExecutor();
    executor.submit(() -> {
      printNumbers(); // first call
      printNumbers(); // second call
    });
    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);
    }
  }
}

Although the executor was shutdown and the thread was interrupted, it prints:

01234567890123456789

This is because there is no thread interruption handling whatsoever.

If you would for example test if the thread was interrupted using Thread.interrupted(), then you can actually deal with an interruption within your thread.

To halt after the first printNumbers you can do:

public class Hello {
  public static void main(String[] args) throws InterruptedException {
    ExecutorService executor = Executors.newSingleThreadExecutor();
    executor.submit(() -> {
      printNumbers(); // first call

      // this checks the interrupt flag of the thread, which was set to
      // true by calling 'executor.shutdownNow()'
      if (Thread.interrupted())
        throw new RuntimeException("My thread got interrupted");

      printNumbers(); // second call
    });
    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);
    }
  }
}

This wil execute the first printNumbers and print:

0123456789

The problem with your example is that whenever you catch InterruptedException, the interrupt flag of the thread is reset to false.

Therefore, if you would execute:

public class Hello {
  public static void main(String[] args) throws InterruptedException {
    ExecutorService executor = Executors.newSingleThreadExecutor();
    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) {
        // the interrupt flag is reset, but do not do anything else
        // except break from this loop
        break;
      }
    }
  }
}

This wil interrupt the first call to printNumbers, but due to the catch of InterruptedException, the thread is not considered interrupted anymore and therefore the second execution of printNumbers will continue and the sleep is never interrupted anymore. The output will be:

0120123456789

However, when you manually set back the interrupt flag after the catch, during the second execution of printNumbers the sleep is interrupted due to the interrupt flag being true and you immediately break out of the loop:

public class Hello {
  public static void main(String[] args) throws InterruptedException {
    ExecutorService executor = Executors.newSingleThreadExecutor();
    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) {
        // the interrupt flag is reset to false, so manually set it back to true
        // and then break from this loop
        Thread.currentThread().interrupt();
        break;
      }
    }
  }
}

It prints 012 from the first printNumbers execution and 0 from the second printNumbers execution:

0120

But remember, this is only because you are manually implementing thread interruption handling, in this case due to using Thread.sleep. As shown by this last example:

public class Hello {
  public static void main(String[] args) throws InterruptedException {
    ExecutorService executor = Executors.newSingleThreadExecutor();
    executor.submit(() -> {
      printNumbers(); // first call
      printNumbersNotInterruptible(); // 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();
        break;
      }
    }
  }

  private static void printNumbersNotInterruptible() {
    for (int i = 0; i < 10; i++) {
      System.out.print(i);
    }
  }
}

This will print out:

0120123456789

This is because printNumbersNotInterruptible does not deal with thread interruption and therefore completes its full execution.

like image 143
chvndb Avatar answered Sep 07 '25 09:09

chvndb