简体   繁体   中英

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?

Thread interruption is not something that kills a thread right away. It is a suggestion for the thread to end themself.

  1. The code in your thread will not terminate automatically. So your code should react to thread interuption by terminating gracefully. One good practice is to reset the interrupted flag everytime it was consumed, by calling Thread.currentThread().interrupt() . Following operations that respect the status will throw InterruptedException, and will not just continue.

  2. Thread.currentThread().interrupt(); should be the first statement in the catch block, to make sure any cleanup doesnt block again. (Using finally for cleanup is also a good idea)

  3. In that specific case I would have pulled the try / catch out of the for loop.

  4. It is basically a suggestion for the thread to end itself.

Java language specification 17.2.3

Interruption actions occur upon invocation of Thread.interrupt, as well as methods defined to invoke it in turn, such as ThreadGroup.interrupt.

Let t be the thread invoking u.interrupt, for some thread u, where t and u may be the same. This action causes u's interruption status to be set to true.

Additionally, if there exists some object m whose wait set contains u, then u is removed from m's wait set. This enables u to resume in a wait action, in which case this wait will, after re-locking m's monitor, throw InterruptedException.

Invocations of Thread.isInterrupted can determine a thread's interruption status. The static method Thread.interrupted may be invoked by a thread to observe and clear its own interruption status.

-

Oracle Tutorial about interrupts

The Interrupt Status Flag The interrupt mechanism is implemented using an internal flag known as the interrupt status. Invoking Thread.interrupt sets this flag. When a thread checks for an interrupt by invoking the static method Thread.interrupted, interrupt status is cleared. The non-static isInterrupted method, which is used by one thread to query the interrupt status of another, does not change the interrupt status flag.

By convention, any method that exits by throwing an InterruptedException clears interrupt status when it does so. However, it's always possible that interrupt status will immediately be set again, by another thread invoking interrupt.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM