Task/Thread cancelation

Java не предоставляет никаких механизмов для безопасной остановки потоков. Вместо этого у нас есть interruption, специальный механизм, который позволяет одному потоку приказать остановиться другому. Как правило редко нужно, чтоб поток остановился незамедлительно, вместо этого он должен закончить свою работу.

public class WhileTrueCancelation implements Runnable {

 private volatile boolean cancelled;
 
 @Override
 public void run() {
  while (!cancelled) {
   // do job
  }
 }
 
 public void cancel() {
  cancelled = true;
 }
}



Но тут может возникнуть проблема, если внутри while цикла вызваются какие-то блокирующие операции, то следующей итерации при которой проверяется флаг может не произойти или произойти она может очень поздно. Поэтому в таких сутуациях лучше использовать механизм interrupt. Ниже пример неудачной остановки потока

public class BrokenProducer implements Runnable {

 private volatile boolean cancelled;
 private final BlockingQueue<Integer> queue;
 
 public BrokenProducer(BlockingQueue<Integer> queue) {
  this.queue = queue;
 }
 
 @Override
 public void run() {
  while (!cancelled) {
 
   try {
    queue.put(produce());
   } catch (InterruptedException e) { }   
  }
 }
 
 public void cancel() {
  cancelled = true;
 }
 
 private int produce() {
  .... slow operation
 }
}




В таких случаях лучше использовать interrupt, ниже пример кода, где вызов метода cancel в случае блокировки на методе put кинет исключение.

При этом поток может быть закблокирован Selector-ом или I/O операции, а также wait, join и т.п. В этом случае вылетит java.lang.InterruptedException. Которое надо как-то обработать, например можно опять вызвать Thread.currentThread().interrupt(), если мы хотим просетать флаг, который проверяем.

При чем обычный лок, созданный блоком synchronized или скажем методом tryLock у Lock не будет никак реагировать на метод interrupt. Если нужно создать лок, который будет реагировать на метод interrupt у потока, внутри которого создается лок, то можно воспользоваться методом lockInterruptibly класса Lock, который кинет InterruptedException, если поток будет прерван

public class GoodProducer extends Thread {
 
 private final BlockingQueue<Integer> queue;
 
 public GoodProducer(BlockingQueue<Integer> queue) {
  this.queue = queue;
 }
 
 @Override
 public void run() {
  while (!Thread.currentThread().isInterrupted()) {
   try {
    queue.put(produce());
   } catch (InterruptedException e) {
    Thread.currentThread().interrupt();
   }   
  }
 }
 
 public void cancel() {
  System.out.println(\"cancel\");
  interrupt();
 }
 
 private int produce() {
                
  return ... slow operation
 }
}


Метод interrupt потока либо выставляет текущий флаг isInterrupted false, если нет блокировки, либо кидает исключение


Аналогично метод interrupted, который возвращает true/false для потока и очищает (флаг isInterrupted в false) статус потока. Этот метод статичен и относится к текущему потоку. В отличии от isInterrupted, который не статичек и является методом класса Thread, этот метод не очищает статус потока

Комментариев нет:

Отправить комментарий