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

Я читаю Java Concurrency In Practice, где это показан пример кода:

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() может быть полезен, — это если он находится внутри цикла, верно?


person Adam    schedule 21.07.2016    source источник


Ответы (4)


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

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

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

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

person John Vint    schedule 21.07.2016
comment
Я не уверен на 100%, что вы пытаетесь сказать. OP явно предполагает, что любой экземпляр данного класса Indexer будет делегатом Thread, и в этом случае выше стека вызовов не будет кода. Кажется, вы говорите: «Не думай об этом!» Вы не знаете, кто вызывает этот метод run(). Я прав? - person Solomon Slow; 21.07.2016
comment
Это верно. Может случиться так, что отношение Indexer к потоку равно 1-1. Я хочу сказать (аналогично Гетцу), что это может быть не так, и если это может быть не так, всегда следует распространять прерывание. - person John Vint; 21.07.2016
comment
Немного грубо сказать, что я думал Thread#stop(). У Runnable просто заканчивается код для запуска после блока try-catch. Не могли бы вы привести пример кода, чтобы продемонстрировать, что вы подразумеваете под Thread ответом на какой-либо другой запрос на отмену? Или хотя бы расширить идею? - person Adam; 22.07.2016

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

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

person Selim Ekizoglu    schedule 20.09.2016

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

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

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

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

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

person Nathan Hughes    schedule 20.09.2016

Документация по 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, может ошибочно предположить, что все в порядке, и что он должен возобновить выполнение в обычном режиме.

person Dirk    schedule 21.07.2016
comment
То есть вы, по сути, говорите, что мистер Гетц мог немного конкретизировать свой пример, чтобы мой вопрос никогда не возник? - person Adam; 22.07.2016
comment
Это один из способов выразить это. Другой может быть: Не останавливайтесь на книге. Также прочитайте документацию и узнайте, что на самом деле делает Thread.interrupt()... - person Dirk; 22.07.2016
comment
пожалуйста, не говорите просто RTFM. К сожалению, javadoc не настолько исчерпывающий. Как поймет каждый, кто читал текущий баннер заголовка SO в документации, примеры здесь являются ключевыми, и эта проблема не решается напрямую, не говоря уже о том, что она проиллюстрирована на примере либо #interrupt(), либо javadoc уровня класса. В вашем примере после вызова interrupt добавляется дополнительный код. Существуют ли какие-либо другие ситуации, когда вызов interrupt может быть последним кодом, выполненным в Runnable, но Thread или связанный с ним Future или что-то еще считывает этот статус прерывания? - person Adam; 23.07.2016
comment
Я думаю, вы неправильно понимаете объем примера, приведенного в книге. Пример здесь для того, чтобы сказать вам, что вам следует делать, а не почему вы должны это делать. Вполне достаточно показать то, что он должен показать. Теперь ваш вопрос касается почему, и в этом контексте фрагмента может быть недостаточно, но вопрос в другом. Да, примеры важны и могут быть полезны, когда вы хотите понять тему. Но так же как и чтение документации. - person Dirk; 24.07.2016