Подтвердить что ты не робот

Является ли Thread.interrupt() злым?

Один товарищ по команде сделал следующее выражение:

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

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

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

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

4b9b3361

Ответ 1

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

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

Нет.

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

Противоположно верно: это важно для многопоточного кода.

См. листинг 7.7 в Java Concurrency на практике для примера.

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

Вокруг здесь мы используем этот метод в одном конкретном месте: обработка 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();
}

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

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

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

Процитировать Brian Goetz от 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 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 могут создаваться и обрабатываться по мере обработки запросов прерываний, но только если этот статус прерывания сохраняется.

Ответ 2

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

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

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

Ответ 3

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

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

Ответ 4

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

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

Ответ 5

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

Ответ 6

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

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

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

Томек

P.S. Я нашел аналогичную проблему.

P.P.S Я прилагаю очень короткий пример того, что я пишу выше. Зарегистрированный класс можно найти в 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() состояние LOST.

Ответ 7

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