Является ли Thread.interrupt () злым? - PullRequest
30 голосов
/ 07 января 2010

Товарищ по команде сделал следующее заявление:

"Thread.interrupt() изначально нарушен, и его (почти) никогда не следует использовать".

Я пытаюсь понять, почему это так.

Известно ли, что лучше не использовать Thread.interrupt()? Можете ли вы предоставить доказательства того, почему он неисправен / содержит ошибки и не должен использоваться для написания надежного многопоточного кода?

Примечание - Меня не интересует этот вопрос, если он "симпатичный" от консерванта дизайна. У меня вопрос - это глючит?

Ответы [ 7 ]

40 голосов
/ 07 января 2010

Короткая версия:

Это известная лучшая практика никогда использовать Thread.interrupt ()?

номер

Можете ли вы предоставить доказательства того, почему он сломан / багги, и не должен использоваться для письма Надежный многопоточный код?

Верно обратное: это критично для многопоточного кода.

См. Пример листинга 7.7 ​​в Параллелизм Java на практике .

Более длинная версия:

Здесь мы используем этот метод в одном конкретном месте: обработка InterruptedExceptions. Это может показаться немного странным, но вот как это выглядит в коде:

try {
    // Some code that might throw an InterruptedException.  
    // Using sleep as an example
    Thread.sleep(10000);
} catch (InterruptedException ie) {
    System.err.println("Interrupted in our long run.  Stopping.");
    Thread.currentThread().interrupt();
}

Это делает для нас две вещи:

  1. Это позволяет избежать употребления исключения прерывания. Обработчики IDE-автоматических исключений всегда предоставляют вам что-то вроде ie.printStackTrace(); и небольшую фразу "TODO: здесь нужно что-то полезное!" комментарий.
  2. Восстанавливает состояние прерывания, не вызывая проверенное исключение для этого метода. Если в сигнатуре метода, которую вы реализуете, нет предложения throws InterruptedException, это другой вариант для передачи этого прерванного статуса.

Комментатор предложил, чтобы я использовал непроверенное исключение ", чтобы заставить поток умирать". Это предполагает, что у меня есть предварительные знания, что резкое уничтожение потока - это то, что нужно делать. Я не.

Чтобы процитировать Брайана Гетца из JCIP на странице перед перечисленным выше списком:

Задача не должна предполагать ничего о политике прерывания ее выполнение потока, если он явно не предназначен для работы в служба с определенной политикой прерывания.

Например, представьте, что я сделал это:

} catch (InterruptedException ie) {
    System.err.println("Interrupted in our long run.  Stopping.");
    // The following is very rude.
    throw new RuntimeException("I think the thread should die immediately", ie);
}

Я бы объявил, что, независимо от других обязательств остальной части стека вызовов и связанного состояния, этот поток должен прекратиться прямо сейчас. Я бы попытался обойти все остальные блоки catch и указать код очистки, чтобы сразу перейти к смерти потока. Хуже того, я бы использовал статус прерванного потока. Теперь вышестоящей логике пришлось бы деконструировать мое исключение, чтобы попытаться выяснить, была ли логическая ошибка программы или я пытаюсь скрыть проверенное исключение внутри скрывающей оболочки.

Например, вот что немедленно должны были сделать все остальные в команде:

try {
    callBobsCode();
} catch (RuntimeException e) { // Because Bob is a jerk
    if (e.getCause() instanceOf InterruptedException) {
        // Man, what is that guy's problem?
        interruptCleanlyAndPreserveState();
        // Restoring the interrupt status
        Thread.currentThread().interrupt();
    }
}

Прерванное состояние важнее любого конкретного InterruptException. Конкретный пример почему см. В javadoc для Thread.interrupt () :

Если этот поток заблокирован при вызове wait (), wait (long), или методы wait (long, int) класса Object или join (), методы join (long), join (long, int), sleep (long) или sleep (long, int), методы этого класса, то его статус прерывания будет очищен, и он будет получить InterruptedException.

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

16 голосов
/ 07 января 2010

Единственный способ, которым я знаю о том, что Thread.interrupt() нарушен, - это то, что он на самом деле не делает то, что кажется, как может - он может только на самом деле прерывать код, который его слушает.

Однако при правильном использовании он мне кажется хорошим встроенным механизмом управления задачами и отмены.

Я рекомендую Java-параллелизм на практике для получения дополнительной информации о правильном и безопасном его использовании.

11 голосов
/ 07 января 2010

Основная проблема с Thread.interrupt() заключается в том, что большинство программистов не знают о скрытых подводных камнях и используют их неправильно. Например, когда вы обрабатываете прерывание, существуют методы, которые сбрасывают флаг (поэтому статус теряется).

Кроме того, вызов не всегда сразу прерывает поток. Например, когда он зависает в какой-то системной процедуре, ничего не произойдет. Фактически, если поток не проверяет флаг и никогда не вызывает метод Java, который выдает InterruptException, то его прерывание не будет иметь никакого эффекта.

3 голосов
/ 07 января 2010

Нет, это не глючит. На самом деле это основа того, как вы останавливаете потоки в Java. Он используется в среде Executor из java.util.concurrent - см. Реализацию java.util.concurrent.FutureTask.Sync.innerCancel.

Что касается неудачи, я никогда не видел, чтобы она выходила из строя, и я широко использовал ее.

1 голос
/ 11 октября 2012

Одна причина, не упомянутая, заключается в том, что сигнал прерывания может быть потерян, что делает бессмысленным вызов метода Thread.interrupt (). Поэтому, если ваш код в методе Thread.run () не вращается в цикле while, результат вызова Thread.interrupt () будет неопределенным.

0 голосов
/ 16 мая 2017

Я заметил, что когда в потоке ONE я выполняю DriverManager.getConnection(), когда нет доступного соединения с базой данных (скажем, сервер отключен, таким образом, наконец, эта строка выдает SQLException) и из потока TWO я явно вызываю ONE.interrupt(), тогда и ONE.interrupted(), и ONE.isInterrupted() возвращают false, даже если поместить в качестве первой строки в блоке catch{}, где обрабатывается SQLException.

Конечно, я обошел эту проблему, реализуя дополнительныесемафор, но это довольно хлопотно, так как это самая первая такая проблема в моей 15-летней разработке Java.

Я полагаю, это из-за ошибки в com.microsoft.sqlserver.jdbc.SQLServerDriver.И я провел дополнительные исследования, чтобы подтвердить, что вызов функции native использует это исключение во всех случаях, когда он передает свое собственное, но сохраняет его при успешном выполнении.

Tomek

PS Я нашел аналогичный выпуск .

PPS Я прилагаю очень короткий пример того, что я пишу выше.Зарегистрированный класс можно найти в sqljdbc42.jar.Я обнаружил эту ошибку в классах, построенных в 2015-08-20, затем обновил ее до последней доступной версии (от 2017-01-12), и ошибка все еще существует.

import java.sql.*;

public class TEST implements Runnable{
    static{
        try{
//register the proper driver
           Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
        }
        catch(ClassNotFoundException e){
            System.err.println("Cannot load JDBC driver (not found in CLASSPATH).");
        }
    }

    public void run() {
        Thread.currentThread().interrupt();
        System.out.println(Thread.currentThread().isInterrupted());
//prints true
        try{
            Connection conn = DriverManager.getConnection("jdbc:sqlserver://xxxx\\xxxx;databaseName=xxxx;integratedSecurity=true");
        }
        catch (SQLException e){
            System.out.println(e.getMessage());
        }
        System.out.println(Thread.currentThread().isInterrupted());
//prints false
        System.exit(0);
    }

    public static void main(String[] args){
        (new Thread(new TEST())).start();
    }
}

Если вы пропустили что-то совершенно неверное, как "foo", на DriverManager.getConnection() вы получите сообщение «Не найден подходящий драйвер для foo», и вторая распечатка будет по-прежнему верной, как и следовало ожидать.Но если вы передадите правильно построенную строку, но, скажем, ваш сервер не работает или вы потеряли сетевое соединение (которое обычно может происходить в производственной среде), вы увидите распечатку ошибки тайм-аута java.net сокета и поток interrupted()состояние потеряно.

0 голосов
/ 07 января 2010

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

...