Остановить, прервать, приостановить и возобновить поток Java

Я начал читать о том, как безопасно останавливать, прерывать, приостанавливать и возобновлять поток Java, я нашел в документации оракула следующие решения:

1- Как безопасно остановить цепочку:

private volatile Thread blinker;

public void stop() {
    blinker = null;
}

public void run() {
    Thread thisThread = Thread.currentThread();
    while (blinker == thisThread) {
        try {
            Thread.sleep(interval);
        } catch (InterruptedException e){
        }
        repaint();
    }
}

- Чтобы остановить поток, я могу использовать переменную boolean вместо volatile Thread, но почему Oracle настаивает на влиянии null на запущенный поток? есть ли какой-нибудь секрет (например, освобождение ресурсов, выделенных с помощью финализатора), который делает это таким образом?

2- Как прервать поток, который ждет долгое время:

public void stop() {
    Thread moribund = waiter;
    waiter = null;
    moribund.interrupt();
}

- почему я должен создавать новую переменную moribund, а не использовать напрямую waiter.interrupt()?

3- Как приостановить и возобновить обсуждение:

private volatile boolean threadSuspended;

public void run() {
    while (true) {
        try {
            Thread.sleep(interval);

            if (threadSuspended) {
                synchronized(this) {
                    while (threadSuspended)
                        wait();
                }
            }
        } catch (InterruptedException e){
        }
        repaint();
    }
}

public synchronized void mousePressed(MouseEvent e) {
    e.consume();

    threadSuspended = !threadSuspended;

    if (!threadSuspended)
        notify();
}

- Почему внутри метода run они добавили цикл while (threadSuspended), потому что я не понимаю, какова цель его добавления, и мой код можно скомпилировать и правильно запустить без него (с такими же результатами).

Ссылка на источник http://docs.oracle.com/javase/8/docs/technotes/guides/concurrency/threadPrimitiveDeprecation.html


person Naruto Biju Mode    schedule 21.05.2014    source источник
comment
Что касается пункта 2, возможно, они поощряют сборку мусора в зависшем потоке.   -  person Totoro    schedule 22.05.2014
comment
@Totoro, но после воздействия null на запущенный поток поток продолжает работать до тех пор, пока он не будет завершен, и gc не запускается напрямую, это занимает время, зависящее от оперативной памяти машины.   -  person Naruto Biju Mode    schedule 22.05.2014
comment
Не могли бы вы дать ссылку на источники, в которых вы нашли указанные примеры? Это дало бы намного больше контекста.   -  person Ordous    schedule 22.05.2014
comment
Да, это сами по себе странные отрывки. Я бы не стал выбирать ни один из этих методов, предоставленный самому себе   -  person Totoro    schedule 22.05.2014
comment
@Ordous я добавил источник ссылки.   -  person Naruto Biju Mode    schedule 22.05.2014


Ответы (1)


1. Использование локальной Thread переменной не позволяет другим потокам вызывать run() метод вашего объекта. Только поток, представленный этим экземпляром объекта, может использовать метод run(). Как правило, вызывать метод run() объекта Thread вручную из другого Thread - это плохая практика, но это, безусловно, возможно.

2. Этот момент необходимо пояснить в контексте пункта 1. В этой части также рассматривается случай, когда interval очень длинный, и поток необходимо остановить как можно скорее.
Вам, безусловно, необходимо аннулировать ссылку, потому что в противном случае код в части 1 просто продолжит цикл. Но подумайте, что может случиться, если вы упростите stop метод до:

public void stop() {
    waiter.interrupt();
    waiter = null;    
}

Поскольку это выполняется из другого потока, его можно каким-либо образом связать с методом run(). Например, threadA вызывает stop(), чтобы остановить threadB, который находится в run():

  1. threadB: сон (интервал)
  2. threadA: waiter.interrupt ()
  3. threadB: поймано InterruptedException
  4. threadB: вызов перерисовки
  5. threadB: введите следующий, пока цикл
  6. threadB: войти в спящий режим (интервал)
  7. threadA: waiter == null

В этом случае, вместо немедленной остановки, threadB выполняет еще один цикл сна, который не выполняет заданную задачу stop a thread that waits for long periods. В данной реализации вы сначала аннулируете, а затем прерываете, что предотвращает такое поведение.

3. Вкратце: потому что другой поток мог уведомить ваш код, не установив правильный флаг. Общий договор notify() заключается в том, что его вызов безвреден (но бесполезный вызов, очевидно, потребует некоторых ресурсов). Предполагается, что все потоки способны справляться с ложными пробуждениями.

person Ordous    schedule 21.05.2014
comment
Большое спасибо за вашу отличную работу, я понял второй пункт, по первому пункту я уже столкнулся с проблемой, которую я разъяснил в этой теме stackoverflow.com/questions/23788838/, я не смог найти отличного решения с использованием внутреннего потока, но он отлично работает с логическим флагом, Что касается третьего пункта, я до сих пор не понимаю, в чем польза от добавления цикла while (threadSuspended), потому что, если я удалю его, мой код отлично работает без него. - person Naruto Biju Mode; 22.05.2014
comment
@NarutoBijuMode Видите ли, вот почему вы должны указывать свои источники, когда задаете вопрос. Приведенный ответ, хотя и правильный, не объясняет реальной проблемы - неправильной реализации вами механизма безопасности. Ссылка должна создаваться в конструкторе, а не в методе подстановки. Что касается пункта 3 - это лучшая практика. Любой метод, использующий wait(), должен зацикливаться, чтобы не умереть, когда кто-то по ошибке разбудил его. - person Ordous; 22.05.2014
comment
Спасибо, что вы действительно гений, для первого пункта мне не удалось реализовать это решение с использованием члена внутреннего потока, созданного внутри класса внешнего конструктора, поэтому я думаю, что буду использовать только простой изменчивый логический флаг для своих приложений Futures, для других точек вы их очень объяснили ну спасибо большое. - person Naruto Biju Mode; 22.05.2014
comment
@Ordous Как первая данная реализация для 2 предотвращает такое поведение? Разве он просто не прервет поток в той же точке, что и с объектом потока официанта, а затем продолжит работу, как будто ничего не произошло? (Предполагая, что он не проверяет, был ли он прерван, чего не происходит в 1) - person Everlight; 12.10.2015
comment
@Everlight Ба, некропостер! Не уверен, что вы подразумеваете под одним и тем же местом в контексте работы двух потоков - некоторые конфигурации невозможны в одном случае, другие - в другом. В этом случае конфигурация, которую мы хотим сделать невозможной, - это конфигурация, в которой после вызова interrupt запускается новый (последний) цикл. Это достигается обнулением потока (поскольку это условие цикла) в качестве предварительного условия для interrupt. - person Ordous; 12.10.2015
comment
@Ordous, я стараюсь =) Когда я говорю то же самое, я имею в виду: Thread.sleep(interval); в первом блоке кода исходного вопроса. Гррр, но, я все еще в замешательстве, вы устанавливаете waiter в ноль, затем прерываете moribund, тогда вы не будете прерваны на Thread.sleep(interval);. Какую конфигурацию вы пытаетесь сделать невозможной, нет? - person Everlight; 13.10.2015
comment
@Everlight Это начало, да. Фактически, в значительной степени предполагается, что interrupt разбудит поток на sleep. То, что будет дальше, под вопросом. В одном случае - объект потока уже обнулен, поэтому цикл while гарантированно не будет выполняться снова. В другом случае один поток вызывает перерисовку, а затем проверяет цикл, а другой устанавливает для официанта значение null. Это состояние гонки, которого избегают, и в зависимости от того, что произойдет первым, может быть или не быть еще одной итерации цикла. - person Ordous; 13.10.2015
comment
Аааааааааааааааааааааа !!! АХ АХ! Я понял это сейчас =) Когда для потока установлено значение null, а затем он прерывается, ссылка на поток отменяется, и он имеет право на сборку мусора. Если его прервать, а затем установить на ноль, у него будет состояние гонки, которое может позволить ему продолжать работать вечно (ish) =) - person Everlight; 14.10.2015
comment
@Everlight Ну почти - это не будет продолжаться вечно, это будет продолжаться до тех пор, пока не будет разыменовано , а затем не будет проверено в цикле. В большинстве случаев это происходит не более чем после одного sleep. Обнуляя, а затем прерывая, мы вызываем проверку сразу после разыменования, без каких-либо задержек или sleep секунд между ними. - person Ordous; 14.10.2015