Комментарии 29
Мне кажется, что если поток завис в managed-коде, то при вызове stop() ничего фатального не произойдет. Хотя зависание в managed-коде это грубая ошибка программы, и в этом месте лучше бы её исправить как-то иначе. А вот если поток передал управление операционной системе и вызов завис в unmanaged-коде, то stop() может привести к краху приложения. В дотнете имеются схожие проблемы, и там вызов Thread.Abort не работает для потоков, выполняющих в момент аборта нативный код, поскольку генерация ThreadAbortException производится только в контексте управляемого кода, и если поток зависнет в нативном коде, его можно будет прибить только вызовом нативной функции Win32, что очень небезопасно. Думаю, в java все устроено примерно так же.
+4
А такой вариант не катит?
private boolean stopped = false;
public void run() {
while (!stoped) {
// code here
}
}
public void stop () {
stopped = true;
}
private boolean stopped = false;
public void run() {
while (!stoped) {
// code here
}
}
public void stop () {
stopped = true;
}
0
Автор говорит в первую очередь о проблеме контроля за модулями, которые написаны другими разработчиками. Они могут написать корявый код, который будет зависать. А сервер приложения ничего не сможет с этим сделать.
0
Именно так и работает interrupt():
while (!interrupted()) {
// code here
}
while (!interrupted()) {
// code here
}
+3
только с volatile, если уж на то пошло. то что Вы написали работать не будет.
0
1) Как вы определите что поток именно «завис» а не выполняет полезную работу? О проблеме остановки машины Тьюринга слышали? Я скажу вам — никак не попределишь. Это фундаметальная проблема которая неразрешима.
2) Никто не пишет jVM — все пишут JVM.
3) Вы явно путаете понятия виртуальной машины и библиотеки классов (Java Class Library/JDK). Говорить, что у JVM нет метода остановки потока — абсурд. Сказали бы уже тогда, что нет инструкции :)
2) Никто не пишет jVM — все пишут JVM.
3) Вы явно путаете понятия виртуальной машины и библиотеки классов (Java Class Library/JDK). Говорить, что у JVM нет метода остановки потока — абсурд. Сказали бы уже тогда, что нет инструкции :)
+12
Спасибо за дельные замечания
1) Определять зависшие потоки можно по разному, в зависимости от того, какие задачи в этих потоках выполняются. В общем случае можно отслеживать время, которое задачи запускаемые в отдельных потоках выполняются и при превышении некоторого лимита выполнять конструкцию Thread.interrupt() и, если Thread.isAlive() по прежнему возвращает true считать поток зависшим.
2) Исправил
3) Исправил
1) Определять зависшие потоки можно по разному, в зависимости от того, какие задачи в этих потоках выполняются. В общем случае можно отслеживать время, которое задачи запускаемые в отдельных потоках выполняются и при превышении некоторого лимита выполнять конструкцию Thread.interrupt() и, если Thread.isAlive() по прежнему возвращает true считать поток зависшим.
2) Исправил
3) Исправил
+1
Может быть, метод Thread.stop() не так уж опасен?
Хм. Это сложный вопрос. Что такое «опасен»? Зависший тред сразу показывает что что-то явно не так с приложением. А если тред убить — то ничего подобного не видно: ошибки могут проявляться не тут, не сейчас, и/или не всегда, и ловить их будет оочень весело. Собственно, все эти причины описаны в тех же жавадоках, как и методы относительно правильных решений этой проблемы: docs.oracle.com/javase/7/docs/technotes/guides/concurrency/threadPrimitiveDeprecation.html
+4
Он опасен хотя бы тем, что после его вызова приложение переходит в неопределённое состояние.
+1
Да, именно прочтя эту доку я и создал тему. На очень животрепещущий вопрос: «What if a thread doesn't respond to Thread.interrupt? (Что делать если поток не откликается на Thread.interrupt)»? Там дается весьма пространный ответ: «In some cases, you can use application specific tricks», что я понимаю как «В некоторых случаях вы можете использовать специфические для вашего приложению трюки». Этот ответ никак нельзя назвать исчерпывающим.
0
На исчерпывающий ответ там претендует другая строка: «Unfortunately, there really isn't any technique that works in general». И это, увы, так: никак нельзя узнать что происходит с другим потоком. Может, он заблокировался на IO в нативном коде, и тогда stop() вообще ничего не даст. А может, он висит на мониторе, и тогда прерывание работы приведет вообще неизвестно к чему. Единственный случай когда может быть и можно рискнуть со stop() — это «вечный цикл», и то, надо держать пальцы крестиком и надеяться что вылетевший в случайном месте ThreadDeath не будет заглушен, корректно обойдет все finally блоки, освободит ресурсы, разблокирует мониторы…
+3
Подобная ситуация с потоками в Python: там поток даже на паузу поставить нельзя.
0
Вообще, я бы делал так:
1. Если каким-то образом удалось определить, что один из потоков «подвис», прибиваем всё приложение. В общем случае, как уже было сказано выше, нельзя определить, есть ли зависший поток, так что, может быть, спасёт какая-нибудь эвристика.
2. Чтобы приложение всё же продолжало работать дальше, делаем guard-процесс.
Чаще всего приложение — это какой-нибудь сервер, в котором потоки обслуживают входящие запросы. В этом случае совсем хорошо было бы сделать так. Рабочий процесс может с guard-процессом общаться каким-нибудь средством IPC (те же сокеты, например). Если рабочий процесс обнаруживает внутри себя зависший поток, он предпринимает следующие действия:
1. Отдаёт guard-процессу запустить и проинициализировать новый процесс, но пока не слушать порт (он ведь слушается рабочим процессом).
2. Освобождает порт и закрывает все соединения.
3. Отдаёт guard-процессу команду о том, что новый процесс может отныне слушать порт.
4. Рабочий процесс прибивает себя.
Так снижается время простоя сервера. Правда, получится, что одновременно будет запущено 3 процесса (из них 2 — рабочих), причём новый рабочий процесс вынужден будет выполнять, возможно дорогую, инициализацию в то время, пока старый рабочий процесс отъедает много ресурсов на зависшем потоке.
Сложно? А что хотели? Thread.stop() действительно небезопасен. Ведь все блокировки слетают и приложение остаётся в неконсистентном состоянии. Дальшейшее его повидение может быть непредсказуемым.
А плагины вот так просто тоже не ставятся на продуктивный сервер. Они должны быть «из доверенных источников». Да и вообще, что значит плагин для сервера? Кто будет делать запросы к этому плагину? Тогда нужно, чтобы и клиент знал про этот плагин и умел им пользоваться.
1. Если каким-то образом удалось определить, что один из потоков «подвис», прибиваем всё приложение. В общем случае, как уже было сказано выше, нельзя определить, есть ли зависший поток, так что, может быть, спасёт какая-нибудь эвристика.
2. Чтобы приложение всё же продолжало работать дальше, делаем guard-процесс.
Чаще всего приложение — это какой-нибудь сервер, в котором потоки обслуживают входящие запросы. В этом случае совсем хорошо было бы сделать так. Рабочий процесс может с guard-процессом общаться каким-нибудь средством IPC (те же сокеты, например). Если рабочий процесс обнаруживает внутри себя зависший поток, он предпринимает следующие действия:
1. Отдаёт guard-процессу запустить и проинициализировать новый процесс, но пока не слушать порт (он ведь слушается рабочим процессом).
2. Освобождает порт и закрывает все соединения.
3. Отдаёт guard-процессу команду о том, что новый процесс может отныне слушать порт.
4. Рабочий процесс прибивает себя.
Так снижается время простоя сервера. Правда, получится, что одновременно будет запущено 3 процесса (из них 2 — рабочих), причём новый рабочий процесс вынужден будет выполнять, возможно дорогую, инициализацию в то время, пока старый рабочий процесс отъедает много ресурсов на зависшем потоке.
Сложно? А что хотели? Thread.stop() действительно небезопасен. Ведь все блокировки слетают и приложение остаётся в неконсистентном состоянии. Дальшейшее его повидение может быть непредсказуемым.
А плагины вот так просто тоже не ставятся на продуктивный сервер. Они должны быть «из доверенных источников». Да и вообще, что значит плагин для сервера? Кто будет делать запросы к этому плагину? Тогда нужно, чтобы и клиент знал про этот плагин и умел им пользоваться.
+3
Язык Java создавался так, чтобы побудить программиста писать максимально безопасный код. Поэтому и были объявлены depricated всякие потенциально опасные операции типа stop(), чтобы заставить программиста более тщательно работать с потоками, а не надеятся на мухобойную stop(). Т.е. вопрос не в JCF, а в архитектуре самого языка. Вряд ли в этом будут какие-то изменения.
+1
Добавлю свои 5 копеек про Андроид:
метод Thread#stop в виртульной машине dalvik не поддерживается вообще. Я задавал вопрос на stackoverflow, но никакого толкового ответа не получил (предложили просто убить приложение) и использовал следующее "грязное" решение:
Если приложение не серверное(в смысле оно относительно часто перезапускается) и его выполнение критично не зависит от работы сторонней библиотеки в которой произошло зависание можно просто выставить потоку минимальный приоритет.
Очевидные минусы: работающая нить тратит системные ресурсы (например, при выполнении на мобильном устройстве это может привести к ускорению разряда батарее).
метод Thread#stop в виртульной машине dalvik не поддерживается вообще. Я задавал вопрос на stackoverflow, но никакого толкового ответа не получил (предложили просто убить приложение) и использовал следующее "грязное" решение:
Если приложение не серверное(в смысле оно относительно часто перезапускается) и его выполнение критично не зависит от работы сторонней библиотеки в которой произошло зависание можно просто выставить потоку минимальный приоритет.
Очевидные минусы: работающая нить тратит системные ресурсы (например, при выполнении на мобильном устройстве это может привести к ускорению разряда батарее).
+2
Это всего лишь сообщение потоку: «Пожалуйста, остановись»
Нуу, интеррупт заставит подключения или методы(wait(), sleep() и пр), которые его поддерживают, плюнуть эксепшн, который потом можно словить и спокойно выйти. То есть если поток ждет на оперциях ввода-вывода из файла, сокета или базы данных(вроде как они тоже это поддержиывают) то его можно впоне безопасно остановить. То же самое и если поток ждет на мониторе или CV.
По-моему это покрывает большинство случаев когда нужно остановить поток. Если вы делаете какие-то сложные вычисления в цикле, не прибегая к никаким операциям ввода-вывода, то тогда уже придется впиливать флаг для остановки.
+5
Да, без сомнения все будет хорошо, если мы имеем дело с правильно написанным кодом. Но если в коде есть ошибка приводящая, например, к бесконечному циклу, то корректно прервать зависший в цикле поток возможности нет.
+2
Имхо, в java архитектурно всё верно решено — в ущерб «простоте и удобству» (сомнительной нужности) остановки некорректного («зависшего») потока предпочли предсказуемость. Метод stop() в его реализации по определению потенциально не может привести ни к чему хорошему.
0
Ребята, я, видимо, что-то не понимаю, но разве в заголовке «Java. Остановись задача» не пропущена запятая? (Если что, прошу простить за дурацкий вопрос и объяснить, в чем ошибся…)
+1
Thread.stop() небезопасен в том плане, что перед уничтожением потока отпускаются все мониторы и блокировки. Таким образом, если поток совершал какие-либо действия над объектом в защищенном коде и не успел завершить логику — остальные потоки получат объект в непредсказуемом состоянии.
По сути, такая ситуация возможна в любом коде, который плохо обрабатывает исключения.
Thread.stop() все таки используется (вместе с ThreadDeath). Другого способа, кроме как постоянно смотреть флаг — нету.
По сути, такая ситуация возможна в любом коде, который плохо обрабатывает исключения.
Thread.stop() все таки используется (вместе с ThreadDeath). Другого способа, кроме как постоянно смотреть флаг — нету.
+1
Думаю, книга Java Concurrency in Practice (легко гуглится) поможет разобраться в ситуации. Целая глава там отведена описанию подходов для прекращения работы потока по требованию. Лично мое мнение — запуская сторонний код, нет вообще никаких гарантий его работоспособности в много-поточной среде.
+3
Так а какое мнение? Да многопоточное программирование на потоках, мюьтексах и барьерах памяти сложно и опасно. Да в этой модели нет фозможности остановить код не спроектированный специально для остановки. Без существенного ограничения на то как используются ресурсы, такие как мьютексы и потоки ввода-вывода, я не представляю как можно это изменить.
Бороться понятно как — писать код корретно обрабатывающий прерывание. Если сторонняя бибилотека этого не умеет — пишем свою. Или убийство процесса и watchdog, смотря какие требования.
Бороться понятно как — писать код корретно обрабатывающий прерывание. Если сторонняя бибилотека этого не умеет — пишем свою. Или убийство процесса и watchdog, смотря какие требования.
0
Дам ссылку на свои размышления по схожему поводу (http://mantonov.blogspot.com/2011/10/blog-post.html), когда стоялаи задачи реализовать сервис по запуску untrusted кода.
Вкратце говоря, если вы запускаете плагины (произвольный код, исполняющийся в вашем процессе JVM), то невозможность убить поток — ваша наименьшая проблема.
Автор плагина может сделать int[] array = new ing[1000000], и весь процесс моментально упадет с OutOfMemoryError, вы и пикнуть не успеете. Или же, в этом плагине может запустить рекурсивная функция без нормальной проверки условия выхода, и вы получаете stack overflow.
Я долго не мог поверить, что это на самом деле так, и читал документацию по Java Security Platform Architecture — но в итоге все подтвердилось, модель безопасности Java не дает вам надежно защититься от сбоев, вызванных чрезмерным потреблением ресурсов.
Самое нормальное решение в случае плагинов (если там действительно требуется серьезные усилия по безопасности) — это запускать весь код в отдельном процессе JVM (под ограниченными правами, выдавая ограниченное количество памяти), и дальше мониторить этот процесс, и убивать в случае превышения лимита по времени работы.
Вкратце говоря, если вы запускаете плагины (произвольный код, исполняющийся в вашем процессе JVM), то невозможность убить поток — ваша наименьшая проблема.
Автор плагина может сделать int[] array = new ing[1000000], и весь процесс моментально упадет с OutOfMemoryError, вы и пикнуть не успеете. Или же, в этом плагине может запустить рекурсивная функция без нормальной проверки условия выхода, и вы получаете stack overflow.
Я долго не мог поверить, что это на самом деле так, и читал документацию по Java Security Platform Architecture — но в итоге все подтвердилось, модель безопасности Java не дает вам надежно защититься от сбоев, вызванных чрезмерным потреблением ресурсов.
Самое нормальное решение в случае плагинов (если там действительно требуется серьезные усилия по безопасности) — это запускать весь код в отдельном процессе JVM (под ограниченными правами, выдавая ограниченное количество памяти), и дальше мониторить этот процесс, и убивать в случае превышения лимита по времени работы.
+8
небольшое дополнение про
(application сервер ввиду имеется?)
вот для того, чтобы память не кончилась, сокетов хватило, лок ресурса не завис и т.д. — подобные «низкоуровневые» операции делать на аппсервере _низя_ и аппсервер сам лучше знает, как этими ресурсами управлять.
Если у нас есть сервер расширяемый с помощью плагинов
(application сервер ввиду имеется?)
вот для того, чтобы память не кончилась, сокетов хватило, лок ресурса не завис и т.д. — подобные «низкоуровневые» операции делать на аппсервере _низя_ и аппсервер сам лучше знает, как этими ресурсами управлять.
0
Я вот думаю, что Thread.stop() зря сделали deprecated. При написании прикладного кода это, безусловно, очень вредная и опасная функциональность. Однако же для системного когда (например, код сервера приложений или, допустим, OSGi контейнера), это, фактически, единственный способ относительно надёжно пристрелить поток.
С другой стороны, есть ещё два класса ситуаций, которые требуют поддержки со стороны JVM и с которыми, на текущий момент, ничего поделать нельзя. Это контроль за памятью отдельного приложения и выгрузка классов.
В принципе, для выгрузки классов можно было бы предусмотреть механизм «форсированной» выгрузки, когда все ссылки на объекты выгруженных классов заменяются на специальное значение unloaded и вызывают исключение при обращении к ним. Я думаю, это можно было бы сделать практически без накладных расходов в рантайме (ну разве что сама операция форсированной выгрузки будет не очень быстрой).
Для OutOfMemory можно было бы предусмотреть несколько изолированных «куч» с контролем границы между ними, но с учётом разделяемых данных это довольно сложно сделать.
Можно рассуждать о том, что это всё проблема кривых приложений, и.т.д, и.т.п. Но факт в том, что программисты делают ошибки, и ничего с этим не поделаешь. Надо как-то это контролировать. Не только багрепортами, но и самим рантаймом. Вот, например, при разработке Erlang это учли, и повальная смерть процессов там — вполне нормальное явление. Конечно, там нет разделяемой памяти, это существенно упрощает дело.
С другой стороны, есть ещё два класса ситуаций, которые требуют поддержки со стороны JVM и с которыми, на текущий момент, ничего поделать нельзя. Это контроль за памятью отдельного приложения и выгрузка классов.
В принципе, для выгрузки классов можно было бы предусмотреть механизм «форсированной» выгрузки, когда все ссылки на объекты выгруженных классов заменяются на специальное значение unloaded и вызывают исключение при обращении к ним. Я думаю, это можно было бы сделать практически без накладных расходов в рантайме (ну разве что сама операция форсированной выгрузки будет не очень быстрой).
Для OutOfMemory можно было бы предусмотреть несколько изолированных «куч» с контролем границы между ними, но с учётом разделяемых данных это довольно сложно сделать.
Можно рассуждать о том, что это всё проблема кривых приложений, и.т.д, и.т.п. Но факт в том, что программисты делают ошибки, и ничего с этим не поделаешь. Надо как-то это контролировать. Не только багрепортами, но и самим рантаймом. Вот, например, при разработке Erlang это учли, и повальная смерть процессов там — вполне нормальное явление. Конечно, там нет разделяемой памяти, это существенно упрощает дело.
0
Есть возможность в долгих циклах вызывать метод ниже.
public static void isInterrupted() throws CancellationException {
if (Thread.currentThread().isInterrupted()) {
throw new CancellationException("Можно указать какой тред, например");
}
}
// пример использования
public static void main() {
while(true) {
isInterrupted();
// какой-то код
}
for (int i = 0; i < 100_000; i++) {
if (i % 100 == 0) {
isInterrupted();
}
}
}
И так далее… Можно просто размазать по коду в любых местах
0
Зарегистрируйтесь на Хабре, чтобы оставить комментарий
Java. Остановись задача