Intereting Posts

Обработка прерывания Exception во время ожидания сигнала выхода (ошибка в Android?)

Я столкнулся с приведенным ниже кодом, и мне интересно, делает ли он именно то, что я думаю:

synchronized(sObject) { mShouldExit = true; sObject.notifyAll() while (!mExited) { try { sObject.wait(); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); } } } 

Об этом контексте: есть другой поток, который проверяет mShouldExit (внутри монитора sObject ) и выйдет в этом случае.

Это не выглядит правильным для меня. Если прерывание произойдет, оно снова установит прерванный статус, поэтому, когда он вернется к sObject.wait() , придет другое прерывание sObject.wait() и т. Д. И т. Д. И т. Д. Поэтому он никогда не сможет перейти в истинное состояние ожидания ( sObject.wait() ), sObject есть он никогда не выпустит монитор sObject . Это может привести к бесконечному циклу, поскольку другой поток не может установить значение mExiting в true, поскольку он никогда не может войти в монитор sObject. (Поэтому я думаю, что вызов interrupt() является ошибкой, его здесь нельзя использовать.) Я что-то упустил?

Обратите внимание, что фрагмент кода является частью исходного исходного кода платформы Android.

ОБНОВЛЕНИЕ: на самом деле ситуация хуже, потому что тот же шаблон используется в Android, когда начинается рендеринг GL. Официальный исходный код GLSurfaceView.GLThread.surfaceCreated() :

  public void surfaceCreated() { synchronized(sGLThreadManager) { if (LOG_THREADS) { Log.i("GLThread", "surfaceCreated tid=" + getId()); } mHasSurface = true; sGLThreadManager.notifyAll(); while((mWaitingForSurface) && (!mExited)) { try { sGLThreadManager.wait(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } } 

Вы можете воспроизвести ошибку аналогично: убедитесь, что ваш поток пользовательского интерфейса имеет свой прерванный флаг состояния, а затем добавьте GLSurfaceView и начните рендеринг GL (через setRenderer(...) , но на некоторых устройствах убедитесь, что ваш GLSurfaceView имеет Visibility.VISIBLE , иначе рендеринг не запускается).

Если вы выполните описанные выше шаги, ваш поток пользовательского интерфейса закончится бесконечным циклом, потому что вышеприведенный код будет продолжать генерировать InterruptedException (из-за wait() ), и поэтому поток GL никогда не сможет установить mWaitingForSurface на false ,

Согласно моим тестам, кажется, что такой бесконечный цикл также приведет к бесконечной последовательности сбора мусора GC_CONCURRENT (или, по крайней мере, таких сообщений в logcat). Интересно, что у кого-то была неизвестная плохо определенная проблема в stackoverflow ранее, которая может быть связана: как решить GC_concurrent освобожден?

Разве не возможно, что, возможно, его поток пользовательского интерфейса имел свой прерванный флаг, установленный в true, и он использовал GLSurfaceView для карты, которую он упоминает? Просто предположение, возможный сценарий.

Solutions Collecting From Web of "Обработка прерывания Exception во время ожидания сигнала выхода (ошибка в Android?)"

Короткая версия: этот код неверен и вызовет бесконечный цикл (у меня все еще есть сомнения, но может зависеть от реализаций JVM). Настройка статуса прерывания – это правильная вещь, но она должна выйти из цикла, в конечном счете, проверить тот же статус прерывания с помощью Thread.isInterrupted ().

Длинная версия для случайного читателя:

Проблема заключается в том, как остановить поток, который в настоящее время выполняет некоторую работу, в ответ на кнопку «Отмена» от пользователя или из-за какой-либо другой логики приложения.

Первоначально Java поддерживал метод «stop», который превентивно останавливал поток. Этот метод был продемонстрирован как небезопасный, потому что не дал остановленному потоку какой-либо (простой) способ очистки, освободить ресурсы, не подвергать частично модифицированным объектам и т. Д.

Итак, Java превратилась в «совместную» систему «прерывания» потока. Эта система довольно проста: поток запущен, кто-то называет это «прерывание», флаг установлен в потоке, ответственность за Thread – проверить, была ли она прервана или нет, и действовать соответствующим образом.

Итак, исправление метода Thread.run (или Runnable.run, Callable и т. Д.) Должно быть примерно таким:

 public void run() { while (!Thread.getCurrentThread().isInterrupted()) { // Do your work here // Eventually check isInterrupted again before long running computations } // clean up and return } 

Это нормально, пока весь код, который выполняет ваш поток, находится внутри вашего метода run, и вы никогда не назовете что-то, что блокирует в течение длительного времени … что часто бывает не так, потому что если вы создаете Thread, Что-то долгое делать.

Самый простой метод, который блокирует Thread.sleep (millis), на самом деле это единственное, что он делает: он блокирует поток за заданный промежуток времени.

Теперь, если прерывание прибывает, пока ваш поток находится внутри Thread.sleep (600000000), без какой-либо другой поддержки, потребуется много для того, чтобы он дошел до точки, где он проверяет isInterrupted.

Есть даже ситуации, когда ваша нить никогда не выйдет. Например, ваш поток что-то вычисляет и отправляет результаты в BlockingQueue с ограниченным размером, вы вызываете queue.put (myresult), он блокирует до тех пор, пока потребитель не освободит место в очереди, если в то же время потребитель был Прервано (или умерло или что-то еще), это пространство никогда не прибудет, метод не вернется, проверка. IsInterrupted никогда не будет выполнена, ваш поток застрял.

Чтобы избежать этой ситуации, все (большинство) методов, которые прерывают поток (должны), бросают InterruptedException. Это исключение просто говорит вам: «Я ждал этого и этого, но в то же время поток как прерванный, вы должны сделать очистку и выйти как можно скорее».

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

InterruptedExceptions еще хуже, поскольку, когда они выбрасываются, «прерванный статус» очищается. Это означает, что просто ловить и игнорировать их приведет к потоку, который обычно не останавливается:

 public void run() { while (!Thread.getCurrentThread().isInterrupted()) { try { Thread.sleep(1000); } catch (InterruptedException e) { // Nothing here } } } 

В этом примере, если прерывание поступает во время метода sleep () (которое равно 99.9999999999% времени), оно будет вызывать InterruptedException, очистить флаг прерывания, тогда цикл будет продолжаться, так как флаг прерывания является ложным, и поток будет Не останавливаться.

Вот почему, если вы правильно выполняете свое «пока», используя .isInterrupted, и вам действительно нужно поймать InterruptedException, и у вас нет ничего особенного (например, очистки, возврата и т. Д.), Чтобы сделать это, в наименьшей степени, что вы можете сделать Снова установлен флаг прерывания.

Проблема в коде, который вы опубликовали, заключается в том, что «while» полагается исключительно на mExited, чтобы решить, когда остановиться, а не ALSO on isInterrupted.

 while (!mExited && !Thread.getCurrentThread().isInterrupted()) { 

Или он может выйти при прерывании:

 } catch (InterruptedException e) { Thread.currentThread().interrupt(); return; // supposing there is no cleanup or other stuff to be done } 

Установка флага isInterrupted в true также важна, если вы не контролируете Thread. Например, если вы находитесь в runnable, который выполняется в пуле потоков какого-либо типа или внутри любого метода в любом месте, где вы не владеете и не управляете потоком (простой случай: сервлет), вы не знаете, Прерывание для «вас» (в случае с сервлетом клиент закрыл соединение, и контейнер пытается остановить вас, чтобы освободить поток для других запросов), или если он нацелен на поток (или систему) в целом ( Контейнер закрывается, останавливая все).

В этой ситуации (что составляет 99% кода), если вы не можете перебросить InterruptedException (что, к сожалению, проверено), единственный способ распространить стек на пул потоков, в котором поток был прерван, устанавливает Перед возвратом верните флаг true.

Таким образом, он будет распространять стек, в конечном итоге генерируя больше InterruptedException, вплоть до владельца потока (будь то сам jvm, Executor или любой другой пул потоков), который может нормально реагировать (повторно использовать поток, позволить ему умереть, System.exit (1) …)

Большая часть этого рассказана в главе 7 Java Concurrency in Practice, очень хорошая книга, которую я рекомендую всем, кто интересуется компьютерным программированием в целом, а не только Java, вызывают проблемы и решения, подобные во многих других средах, и объяснения Очень хорошо написано.

Почему Sun решила сделать InterruptedException проверенной, когда в большинстве документов предлагается безжалостно реконструировать ее, и почему они решили очистить прерванный флаг, когда бросают это исключение, когда правильная вещь – это снова установить его значение true в большинстве случаев, остается открытым Для обсуждения.

Однако, если .wait освобождает блокировку, прежде чем проверять флаг прерывания, он открывает небольшую дверь из другого потока, чтобы изменить mExited boolean. К сожалению, метод wait () является родным, поэтому источник этой конкретной JVM должен быть проверен. Это не меняет того факта, что код, который вы опубликовали, плохо кодируется.