Является ли этот пример кода простым или может быть причина использовать interrupt()

Я читаю Java Concetrency на практике Гетца, где показан этот пример кода:

public final class Indexer implements Runnable {

    private final BlockingQueue<File> queue;

    public Indexer(BlockingQueue<File> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        try {
            while (true) {
                queue.take();
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

}

с описанием:

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

В примере кода "код выше по стеку вызовов" никогда не увидит прерывания, если этот код выполняется - или я делаю неправильный вывод? Нить тут просто умирает после звонка interrupt(), правильный?

Так что единственный способ это interrupt() может быть полезно, если это внутри цикла, правильно?

4 ответа

Поток здесь просто умирает после вызова interrupt(), правильно?

Вы выполняете поток не закончится после прерывания, вы думаете Thread#stop, Поток потока может продолжать работать даже после завершения запуска. Runnable - это просто задача, выполняемая потоком.

После выполнения этой задачи важно ли, чтобы поток знал, что произошло прерывание? Что если поток должен ответить на какой-либо другой запрос отмены, и это делается другим потоком?

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

В Java вам нужно обработать прерывание потока. Чтобы сделать этот опрос Thread.isInterrupted() является необходимым. Когда ваш код является частью Runnable вам нужно установить прерванный флаг потока в случае InterruptedException так что последующие задачи, выполняемые этим потоком (код выше стека вызовов) могут опрашивать Thread.isInterrupted() и обрабатывать прерывания соответствующим образом. Это также дает потоку шанс выйти чисто, а не убивать его сразу. Это то, что Thread.stop() делает.

Object.wait а также Thread.sleep два примера, которые сначала проверяют флаг и бросают InterruptedException если это установлено.

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

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

Если Runnable передается в Executor, то Executor отвечает за установку флага прерывания в своих потоках. Также Исполнитель должен знать лучше, чем полагаться на задачи по восстановлению флага прерывания. Поэтому обычно не должно иметь значения, если задача Executor не восстанавливает статус прерывания.

Но могут быть крайние случаи; Runnable, возможно, должен быть запущен текущим потоком в отличие от передачи другому потоку. Например, если вы отправляете задачу Исполнителю, у которого есть политика отклонения, которая заставляет задачу выполняться в вызывающем потоке, то прерывания в вызывающем потоке могут быть потеряны, если выполняется задача, отклоненная исполнителем, который не выполняет восстановить флаг прерывания.

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

Документация Java определяет эффект Thread.interrupt() как

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

Если этот поток заблокирован в операции ввода-вывода на InterruptibleChannel, то канал будет закрыт, состояние прерывания потока будет установлено, и поток получит исключение ClosedByInterruptException.

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

Если ни одно из предыдущих условий не выполняется, то статус прерывания этого потока будет установлен.

Обратите внимание на последний абзац; по существу, если ваш поток в настоящее время не выполняет одну из упомянутых операций в стиле ожидания, прерывание потока просто устанавливает флаг, но в остальном не влияет на поток управления. Это верно независимо от того, поступил ли вызов из другого потока или самого потока-жертвы.

Другими словами:

try {
   while (true) {
        queue.take();
    }
} catch (InterruptedException e) {
    Thread.currentThread().interrupt();
}

// When an interrupt is received, the thread resumes
// execution here. It will notice the effect of the
// Thread.currentThread().interrupt() only, if it tries
// to perform any of the mentioned special wait operations
// here.

synchronized (someLockObject) {

    while (!someCondition) {

        // Will immediately terminate with an `InterruptedException`,
        // since we properly restored the interrupt status
        // above.

        someLockObject.wait();
    }
}

Этот метод достигает того, что весь код в текущем стеке вызовов имеет шанс распознать прерывание и выполнить необходимую очистку и "упорядоченное" завершение работы. Если вы не восстановите состояние прерывания, то код, следующий за try/catch может ошибочно предположить, что все в порядке, и что он должен возобновить выполнение в обычном режиме.

Другие вопросы по тегам