Когда должен быть метод throw InterruptedException и как я должен обрабатывать тот, который делает? (метод блокировки)

Если метод должен быть блокирующим методом, я прав, думая, что если я уйду out throws InterruptedException, я допустил ошибку?

В двух словах:

  • Метод блокировки должен включать throws InterruptedException в противном случае - обычный метод.
  • Метод блокировки может скомпрометировать отзывчивость, потому что может быть трудно предсказать, когда он завершит то, зачем ему нужно throws InterruptedException.

Правильно ли это?

Ответ 1

Нет, я не считаю ваше резюме правильным. Обычно, если вы пишете метод, который вызывает других, которые бросают InterruptedException, тогда ваш метод также должен рекламировать throwing InterruptedException, если у вас есть хороший план того, что делать, когда методы, на которых вы полагаетесь, прерывают прерывание сигнала.

Случаи, когда вы сможете поглотить такое прерывание, редки. Возможно, вы вычисляете итеративное решение, где точность возрастает со временем, но, когда прерывается ваш вызывающий поток, вы решаете, что решение, которое вы достигли в течение отведенного времени, достаточно хорошее и все еще достаточно корректно для возврата. Другими словами, это решение все еще находится в пределах диапазона ваших методов.

Представьте себе:

private double improveUpon(double start) throws InterruptedException {
  // ...
}


public double compute() {
  double result = 0.0;
  try {
    do {
      result = improveUpon(result);
    } while (couldBeImproved(result));
  } catch (InterruptedException ex) {
    Thread.currentThread().interrupt();
  }
  return result;
}

В качестве альтернативы, если вы просто хотите соблюдать запрос прерывания, вы можете сделать это без участия InterruptedException:

private double improveUpon(double start) {
  // ...
}


public double compute() {
  final Thread current = Thread.currentThread();
  double result = 0.0;
  do {
    result = improveUpon(result);
  } while (couldBeImproved(result) &&
           !current.isInterrupted());
  return result;
}

В еще одном варианте рассмотрим случай, когда ваш метод должен либо завершить всю свою работу, либо указать вызывающему, что он не может его завершить, и требуется некоторое время, чтобы добраться туда, но вы хотите уважать прерывание потока. Что-то вроде этого будет достаточно:

private double improveUpon(double start) {
  // ...
}


public double compute() throws InterruptedException {
  final Thread current = Thread.currentThread();
  double result = 0.0;
  do {
    if (current.interrupted())
      throw new InterruptedException();
    result = improveUpon(result);
  } while (!isAdequate(result));
  return result;
}

Обратите внимание, что мы вызвали Thread#interrupted(), который имеет побочный эффект очистки статуса прерывания потока, если он был установлен. Если этот метод возвращает true, мы, как вызывающий, приняли на себя ответственность за сохранение и сообщение об этом состоянии прерывания. В этом случае, поскольку мы не предполагаем, что мы создали вызывающий поток, и у нас недостаточно видимой области видимости, чтобы узнать, что такое его политика прерывания, мы сообщили о состоянии прерывания, которое мы наблюдали и приняли, выбрасывая InterruptedException.

Маркировка метода как "блокировки" всегда является вопросом степени; каждый метод блокирует его вызывающего абонента на некоторое количество времени. Различие, которое вы, возможно, ищете, заключается в том, блокирует ли метод блокирование на каком-либо внешнем входе, например, нажатие клавиши или сообщение, поступающее по сети. В тех случаях реклама, которую вы бросаете InterruptedException, указывает вашему абоненту, что ваш метод безопасен для использования вызывающими пользователями из потоков, которые должны контролировать свою задержку. Вы говорите: "Это может занять некоторое время, но это займет не больше времени, чем вы готовы ждать". Вы говорите: "Я убегу, пока ты не скажешь мне". Это отличается от, скажем, java.io.InputStream#read(), что грозит блокировать до тех пор, пока не произойдет одно из трех условий, ни один из которых не прерывается нить вызывающего.

В большинстве случаев ваше решение сводится к ответу на следующие вопросы:

  • Чтобы удовлетворить требованиям моего метода, мне нужно вызвать любые методы, которые бросают InterruptedException?
  • Если это так, то работа, которую я сделал до этой точки, для моего вызова?
  • Если нет, я тоже должен бросить InterruptedException.
  • Если ничего не вызывается throws InterruptedException, должен ли я соблюдать статус прерывания вызывающего потока?
  • Если да, то есть ли какая-либо работа, которую я сделал до того момента, когда я обнаружил, что меня прервал какой-либо доступ к моему абоненту?
  • Если нет, я должен бросить InterruptedException.

Ситуации, в которых каждый обнаруживает текущее прерывание потока и проглатывают его, обычно ограничиваются теми, где вы, автор, создали соответствующий поток, и вы обязались выйти из потока run(), как только поток получит прерываться. Это понятие "совместное аннулирование", в котором вы наблюдаете просьбу о том, что ваш поток перестает работать, и вы решили выполнить этот запрос, как можно быстрее завершив свою работу и разрешив стеку вызовов потоков. Опять же, если вы не являетесь автором метода потока run(), вы проглатываете состояние прерывания потока, вероятно, наносите вред предполагаемому поведению ваших абонентов и других методов, на которые они звонят.

Я предлагаю вам изучить тему статуса прерывания потока и получить удобство с методами Thread#isInterrupted(), Thread#interrupted() и Thread#interrupt(). Как только вы это поймете и увидите, что InterruptedException находится в полете, это альтернативное представление Thread#isInterrupted(), вернувшееся true, или вежливый перевод Thread#interrupted(), вернувшийся true, все это должно стать более понятным.

Если вам нужно больше примеров для изучения, скажите об этом, и я могу добавить здесь рекомендации.

Ответ 2

InterruptedException (обычно) вызывается, когда поток, заблокированный методом, вызывает interrupt().

Точка - разблокировать (по какой-то причине) поток, который заблокирован. Пример причины - это выключение приложения. Итак, когда вы завершаете свое приложение, если у вас есть потоки, ожидающие, скажем sleep() или wait(), если вы не сообщите им, что вы завершаете их, они продолжат wait(). Если эти потоки не являются потоками демона, ваше приложение не отключится.

Итак, когда поток прерывается во время sleep(), вы должны проверить условия и обработать ситуацию. В случае выключения вы должны проверить свой флаг shutdown и, в конце концов, выполнить очистку и оставить поток.

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

Ответ 3

Если ваш метод блокирует, он должен ловить и обрабатывать InterruptedException, и предпочитает не перебрасывать его.

Кроме того, метод может блокироваться в нескольких местах - каждое место должно ловить и обрабатывать InterruptedException способом, подходящим для места, где его можно было бы выбросить.

Библия на тему многопоточного кода Java Concurrency на практике. Я настоятельно рекомендую вам прочитать его.

Отредактировано:

При разработке своего параллельного кода поймите, что:

  • В соответствии с спецификацией JVM InterruptedException может быть случайно запущен JVM без каких-либо оснований (известный как "побочные пробуждения" )
  • Многие потоки могут ждать в одном и том же состоянии, все могут быть разбужены (например, notifyAll()), но только один может продвигаться при прерывании

поэтому всякий раз, когда пробуждается поток, он должен проверять состояние ожидающего условия, в котором он ожидает, и, возможно, вернуться к ожиданию.

Таким образом, правильно написанный параллельный код должен ловить InterruptedException. Вы можете выбрать повторное бросить его или бросить свое собственное исключение для конкретного приложения. Методы "Код приложения" должны предпочесть бросать исключения "приложения", однако, если ваш код ожидания может оказаться в состоянии, когда невозможно определить "что пошло не так", тогда ваш единственный вариант - бросить InterruptedException.