Подтвердить что ты не робот

Являются ли Thread.stop и друзьями безопасными в Java?

stop(), suspend() и resume() в java.lang.Thread устарели, потому что они небезопасны. Солнце рекомендовало использовать Thread.interrupt(), но этот подход не работает во всех случаях. Например, если вы вызываете метод библиотеки, который явно или неявно не проверяет флаг interrupted, у вас нет выбора, кроме как дождаться завершения вызова.

Итак, мне интересно, можно ли охарактеризовать ситуации, когда (доказуемо) безопасно называть stop() в потоке. Например, было бы безопасно stop() поток, который ничего не делал, кроме вызова find(...) или match(...) на java.util.regex.Matcher?

(Если есть какие-либо инженеры Sun, которые читают это... окончательный ответ будет действительно оценен.)

РЕДАКТИРОВАТЬ: ответы, которые просто повторяют мантру, которую вы не должны называть stop(), потому что она устарела, небезопасна, независимо от того, отсутствует точка этого вопроса. Я знаю, что в большинстве случаев это действительно небезопасно, и что если есть жизнеспособная альтернатива, вы должны всегда использовать это вместо этого.

Этот вопрос касается случаев подмножества, где он безопасен. В частности, что это за подмножество?

4b9b3361

Ответ 1

Здесь моя попытка ответить на мой собственный вопрос.

Я думаю, что для того, чтобы один поток был безопасно остановлен с помощью Thread.stop(), должно быть достаточно следующих условий:

  • Выполнение потока не должно создавать или мутировать любое состояние (например, объекты Java, переменные класса, внешние ресурсы), которые могут быть видны другим потокам в случае остановки потока.
  • Выполнение потока не должно использовать notify для любого другого потока во время его нормального выполнения.
  • Нить не должна start или join других потоков или взаимодействовать с ними с помощью stop, suspend или resume.

(Термин "выполнение потока" выше охватывает весь код уровня приложения и весь код библиотеки, который выполняется потоком.)

Первое условие означает, что остановленный поток не оставляет никаких внешних структур данных или ресурсов в несогласованном состоянии. Это включает в себя структуры данных, к которым он может обращаться (чтение) в пределах мьютекса. Второе условие означает, что поток с остановкой не может оставить ожидание другого потока. Но это также запрещает использование любого механизма синхронизации, кроме того, что простые мьютексы объекта.

Стоповый поток должен иметь способ доставить результаты каждого вычисления в управляющий поток. Эти результаты создаются/мутируются потоком stoppable, поэтому нам просто нужно убедиться, что они не видны после остановки потока. Например, результаты могут быть назначены частным членам объекта Thread и "защищены" с флагом, который атомизирован потоком, чтобы сказать, что он "сделан".

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

  • Методы Pattern.compile(...) могут обновлять статический кеш скомпилированных шаблоны, и если бы они это сделали, они (должны) использовали бы мьютекс, чтобы сделать это. (На самом деле, версия OpenJDK 6.0 не кэширует шаблоны, но Sun, возможно, может изменить это.)
  • Если вы попытаетесь избежать 1), скомпилировав регулярное выражение в потоке управления и предоставив предварительно созданный Matcher, то поток регулярных выражений будет мутировать внешнее видимое состояние.

В первом случае мы, вероятно, будем в беде. Например, предположим, что HashMap был использован для реализации кэша и что поток был прерван во время реорганизации HashMap.

Во втором случае мы были бы в порядке, если Matcher не был передан в какой-либо другой поток, и при условии, что поток контроллера не попытался использовать Matcher после остановки потока совпадений регулярных выражений.

Итак, где это нам остается?

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

Это имеет смысл? Я что-то пропустил?

РЕДАКТИРОВАТЬ 2

Вещи становятся немного более волосатыми, если вы считаете, что код, который мы могли бы пытаться убить, может быть недоверенным:

  • Мы не можем полагаться на "promises"; например аннотации к ненадежному коду, что он либо убиваем, либо не убиваем.

  • Нам действительно нужно иметь возможность остановить ненадежный код от выполнения действий, которые сделают его unkilable... в соответствии с указанными критериями.

Я подозреваю, что это повлечет за собой изменение поведения JVM (например, внедрение ограничений времени выполнения, для которых потоки разрешены для блокировки или изменения) или полная реализация Isolates JSR. Это выходит за рамки того, что я рассматривал как "честную игру".

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

Ответ 2

Отсутствие безопасности исходит из идеи идеи критических секций

Take mutex

do some work, temporarily while we work our state is inconsistent

// all consistent now

Release mutex

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

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

(Очень надуманный) Пример возможной небезопасности. У нас есть связанный список, он не циклический. Все алгоритмы действительно zippy, потому что мы знаем не циклические. Во время нашего критического раздела мы временно вводим цикл. Затем мы сдуемся, прежде чем мы выйдем из критической секции. Теперь все алгоритмы, использующие цикл списка навсегда. Никакой автор библиотеки не сделал бы этого, конечно! Откуда вы знаете? Вы не можете предположить, что используемый вами код хорошо написан.

В примере, на который вы указываете, можно, безусловно, написать требуемую функциональность прерывистым способом. Больше работы, но можно быть в безопасности.

Я возьму флайер: нет документального подмножества объектов и методов, которые можно использовать в аннулируемых потоках, потому что автор библиотеки не хочет делать гарантии.

Ответ 3

Может быть, есть что-то, чего я не знаю, но как java.sun.com сказал, что это небезопасно, потому что все, что обрабатывает этот поток, находится в серьезный риск быть поврежденным. Другие объекты, соединения, открытые файлы... по понятным причинам, например, "не закрывайте свое Word без сохранения в первую очередь".

Для этого find(...), на самом деле, я действительно не думаю, что это была бы катастрофа, чтобы просто отбросить его с помощью sutiless .stop()...

Ответ 4

Конкретный пример, вероятно, поможет здесь. Если кто-то может предложить хорошую альтернативу следующему использованию остановки, мне было бы очень интересно. Повторная запись java.util.regex для поддержки прерывания не учитывается.

import java.util.regex.*;
import java.util.*;

public class RegexInterruptTest {

    private static class BadRegexException extends RuntimeException { }
        final Thread mainThread = Thread.currentThread();
        TimerTask interruptTask = new TimerTask() {
            public void run() {
                System.out.println("Stopping thread.");
                // Doesn't work:
                // mainThread.interrupt();
                // Does work but is deprecated and nasty
                mainThread.stop(new BadRegexException());
            }
        };

        Timer interruptTimer = new Timer(true);
        interruptTimer.schedule(interruptTask, 2000L);

        String s = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab";
        String exp = "(a+a+){1,100}";
        Pattern p = Pattern.compile(exp);
        Matcher m = p.matcher(s);
        try {
            System.out.println("Match: " + m.matches());
            interruptTimer.cancel();
        } catch(BadRegexException bre) {
            System.out.println("Oooops");
        } finally {
            System.out.println("All over");
        }
    }
}

Ответ 5

Есть способы использовать Thread.stop() относительно стабильно без утечки памяти или файловых дескрипторов (FDs исключительно подвержены утечке в * NIX), но вы должны полагаться на него, только если вам нужно управлять сторонним кодом. Никогда не используйте его для достижения результата, если вы можете контролировать сам код.

Если я использую Thread.stop вдоль w/interrupt() и еще несколько хакеров, например добавление пользовательских обработчиков ведения журнала, чтобы повторно бросить захваченный ThreadDeath, добавив unhandleExceltionHandler, работающий в вашей собственной ThreadGroup (sync over 'em) и т.д...

Но это заслуживает совершенно новой темы.

Но в этом случае вам говорят Java-дизайнеры; а также   они более авторитетны на своем языке, чем любой из нас:)

Просто примечание: немало из них довольно невежественны

Ответ 6

Если мое понимание прав, проблема связана с тем, что блокировки синхронизации не освобождаются, поскольку сгенерированный ThreadInterruptedException() распространяется вверх по стеку.

Принимая это как должное, он по своей сути небезопасен, потому что вы никогда не узнаете, был ли вызван или осуществлен какой-либо "вызов внутреннего метода", в котором вы оказались в тот момент, когда stop() был вызван и осуществлен, эффективно удерживая блокировку синхронизации и то, что говорят инженеры java, похоже, недвусмысленно правильно.

То, что я лично не понимаю, заключается в том, почему не должно быть возможности освободить блокировку синхронизации, поскольку этот конкретный тип Exception распространяется по стеку, тем самым передавая все разделители блоков функций/блоков синхронизации, которые вызывают блокировки для освобождения для любого другого исключения.

У меня есть сервер, написанный в java, и если администратор этой службы хочет "холодное завершение", то просто НЕОБХОДИМО, чтобы иметь возможность остановить все текущие действия независимо от того, что. Согласованность любого состояния объекта не вызывает беспокойства, потому что все, что я пытаюсь сделать, это EXIT. Как можно быстрее.

Ответ 7

Нет надежного способа убить поток.

Также нет подмножества ситуаций, где это безопасно. Даже если он работает на 100% при тестировании в Windows, это может привести к повреждению памяти процесса JVM в Solaris или утечке ресурсов потока в Linux.

Следует всегда помнить, что под Java Thread существует реальный, родной, небезопасный поток.

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

Невозможно, чтобы Java-машина учитывала все возможные последствия, поскольку поток может выделять/использовать ресурсы не только в процессе JVM, но и в ядре ОС.

Другими словами, если встроенная библиотека потоков не обеспечивает безопасный способ kill() потока, Java не может предоставить никаких гарантий лучше этого. И все известные мне встроенные реализации утверждают, что убивающий поток - опасный бизнес.

Ответ 8

Все формы управления concurrency могут быть предоставлены примитивами синхронизации Java, построив более complex concurrency элементы управления, которые соответствуют вашей проблеме.

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

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

Изменить: Я повторю причину устаревания, а также как их избежать.

Поскольку единственная опасность заключается в том, что объекты, на которые может ссылаться поток stop ed, могут быть повреждены, просто clone String, прежде чем передать его в Thread. Если общих объектов не существует, угроза поврежденных объектов в программе вне stop ed Thread больше не существует.