LINUX.ORG.RU

Java - список активных потоков

 ,


0

2

Продолжаю мучить свою программу. У меня Swing GUI интерфейс, в нем запускаю на выполнение разные задачи в отдельных потоках, все по фэн-шую, потоки фоном выполняются (от мгновения до долгого времени), интерфейс живет и не зависает. Хочу сделать на форме динамически самообновляемый список выполняющихся потоков. Сделал, при запуске новой задачи ее поток добавляется в список. Хочу чтобы при окончании сама удалялась из списка - сделал (нашел на стэковерфлоу десяток вариантов). Хочу чтобы по эскейпу выделенная задача прерывалась - сделал, но при этом она не удаляется из списка, видимо прерывается поток не доходя до конца отладочный принт показывает, что до верхнего уровня вызывающей попытки дело не доходит, видимо прерывается с концами где-то посреди внутренних функций. Хорошо, я могу пытаться добиться, чтобы исключение пробрасывалось до верха и удалять текущую задачу из списка. Но есть деление на 0. Хорошо, отловлю и его, но есть переполнение стека (в яве у меня постоянно) и куча других причин, по которым система прибьет поток, а мне не скажет. Я могу получить список активных потоков в любой момент. Могу повесить это дело на таймер и проверять каждую секунду. Но по-моему это криво и должен быть красивый способ. Как запросить у системы универсальный коллбэк при завершении (любом) любого из моих потоков? Готов согласиться даже на Swing Worker с его ограничением в 10 потоков, если только он это умеет их хорошо и удобно администрировать.


Вести с полей - throws Throwable и catch(Throwable e) {cout(true, e.getLocalizedMessage())} вроде решают часть вопросов, Thread.UncaughtExceptionHandler должен решить еще часть... Если объединение этих частей будет меньше заданного универсума, то выделю нерешенную часть отдельно.

Ivana ()
Ответ на: комментарий от Ivana

Всё правильно. Поток не может завершиться внезапно просто так. Он всегда завершается из-за исключения. Поэтому его можно ловить тем или иным образом.

Ещё непонятно — как ты завершаешь поток по Esc. Правильный способ — interrupt от GUI, проверка флага interrupted внутри потока и завершение работы тем или иным образом. Неправильный способ — thread.stop.

Legioner ★★★★★ ()
Ответ на: комментарий от Legioner

Нет, я стараюсь читать в инете про то что депрекейтед и ансейф и не использовать это. Поток завершаю примерно так - вешаю вот это

Action interrupt = new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { cout(true, «interrupt»); int ind = threadList.getSelectedIndex(); InterThread thread2delete = threadListModel.elementAt(ind); thread2delete.interrupt(); cout(true, thread2delete.getName()); } };

на кнопку и дублирую на клавишу эскейп. А потом внутри функций потока

if (Thread.currentThread().isInterrupted()) { //throw new InterruptedException(); Thread.currentThread().interrupt(); throw new Error(); //return null; }

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

ЗЫ я начинаю подозревать, что Java - язык классового неравенства и борьбы/подчинения/зависимости, все как по Марксу. Были сложности с числами - посмотрел иерархию классов чисел и их методы, все понятно. С исключениями - иерархию исключений, их возникновение, и волшебное откровение, что если возникшее исключение не обработать, оно честно пробросится выше по стеку вызовов. В общем, открываю для себя азы. Такими темпами у меня в программе скоро не один класс мэйн будет, а даже разнесение слабосвязанной логики по разным классам-файлам, но это попозже буду делать )

Ivana ()
Ответ на: комментарий от Ivana

С interrupted всё просто. У потока есть внутри логическое поле — прерван он или нет. По умолчанию, конечно, не прерван. При вызове thread.interrupt сначала проверяется — что поток делает. Если поток спит на блокирующем вызове (например ждёт ввода пользователя или пока ему ответит удалённый сервер или просто вызвал sleep), то этот блокирующий вызов прерывается и из него выбрасывается InterruptedException. Ну а дальше уже код отрабатывает — что делать с этим InterruptedException-м. А если поток не спит, а, например, считает факториал миллиона, тогда исключение бросать негде (не бросишь же исключение посреди умножения двух чисел), поэтому просто у потока выставляется флажок interrupted и предполагается, что программист периодически его будет проверять и реагировать на его выставление.

Legioner ★★★★★ ()
Ответ на: комментарий от Legioner

Это как раз то, что мне понятно про прерывание потока. Непонятно другое - как мне лучше реагировать, если я проверил и обнаружил выставленный флажок. Выбрасывать эррор, снова выставлять этот флажок, или и то и другое или что еще... Мне спасать ничего не надо, мне бы из глубин рекурсии быстро выйти с текстовым описанием «прервано пользователем», удалить поток из списка активных и надеяться что система тут же завершит поток, а сборщик мусора очистит место, которое занимал его экземпляр при жизни.

Ivana ()
Ответ на: комментарий от Ivana

Во всех интернетах написано, что надо делать

if (Thread.currentThread().isInterrupted()) { Thread.currentThread().interrupt(); }

а я сделал

if (Thread.currentThread().isInterrupted()) { throw new Error(«interrupted by user.....»); }

и все работает как я хочу. Может это и неправильно, но пока оставлю так.

Ivana ()
Ответ на: комментарий от Ivana

Это как раз то, что мне понятно про прерывание потока. Непонятно другое - как мне лучше реагировать, если я проверил и обнаружил выставленный флажок. Выбрасывать эррор, снова выставлять этот флажок, или и то и другое или что еще... Мне спасать ничего не надо, мне бы из глубин рекурсии быстро выйти с текстовым описанием «прервано пользователем», удалить поток из списка активных и надеяться что система тут же завершит поток, а сборщик мусора очистит место, которое занимал его экземпляр при жизни.

Как угодно можешь делать. Система завершит поток, когда метод перегруженный метод Runnable.run завершит выполнение, нормально или исключением — жаве всё равно. Сборщик мусора очистит место, когда его алгоритмы решат это сделать, если ссылок на это место ниоткуда нет, то оно будет очищено, про это можно сильно не задумываться.

Error лучше не кидать, принято использовать этот класс для самых страшных ошибок. Лучше кидать RuntimeException или сделать своего наследника. Виртуальной машине без разницы, вопрос именно в общепринятых соглашениях. А ещё лучше обойтись без исключения. Например так:

    @Override
    public void run() {
        boolean stop = false;
        while (!stop) {
            try {
                doWork();
                if (Thread.interrupted()) {
                    stop = true;
                }
            } catch (InterruptedException ignored) {
                stop = true;
            } catch (Exception e) {
                e.printStackTrace(); // report error
                stop = true;
            }
        }
        
        removeFromRunningThreads(this);
    }
Legioner ★★★★★ ()
Ответ на: комментарий от Ivana

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

public class FooBar {
  private AtomicBoolean running;

  private Thread thread;

  public void start() { 
    thread = new Thread(() -> { 
      running.set(true);
      while (running.get()) { try { ... } finally { running.set(false); } } 
    });

    thread.start(); 
  }

  public boolean isRunning() { 
    return thread.isAlive();
  } 

  public void stop() { 
    running.set(false); 
    thread.interrupt(); 
  }
}

Соответственно, где-то хранишь экземпляры каждого потока. Например, в Map. Когда нужно остановить поток делаешь:

threads.get("fooBar").stop()

Когда нужно проверить состояние, делаешь

threads.get("fooBar").isRunning()
foror ★★★★ ()
Ответ на: комментарий от Legioner

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

    class InterThread extends Thread {
        public void run() {
            try {
                Object v = eval(globalEnv, lv);
                cout(true, v.toString());
            } catch (Throwable e) {
                cout(true, e.toString());
                Thread.currentThread().interrupt();
            }
            if (threadListModel.contains(this))
                threadListModel.remove(threadListModel.indexOf(this));
        }
    }
В принципе как у вас. И если в этот ран все равно прилетают куча исключений при делении на 0, переполнении стека и т.п., то не вижу ничего страшного (а вижу унификацию и удобство), если при проверке флага прерывания мой eval будет тоже кидать исключение. Но может потом поумнею и пойму почему вы предлагаете делать по-другому.

Ivana ()
Ответ на: комментарий от foror

foror, спасибо за ответ. Но я позволю себе наглость не согласиться ни по одному пункту. Исключения кидать мне разрешили.

.stop()
мне наоборот запретили. А проверять состояние я умею, но мне не надо.

ЗЫ после вашей рекомендации как надо останавливать поток, к остальным вашим рекомендациям я отношусь с предубеждением.

Ivana ()
Ответ на: комментарий от Ivana

Исключения кидать мне разрешили

Кто этот авторитетный гуру? )

мне наоборот запретили

Я не говорю про Thread#stop, я говорю про FooBar#stop, код этого stop предоставил выше.

А проверять состояние я умею, но мне не надо.

Код в студию.

Ваш пример с попыткой в цикле не понял

Его код сокращенная версия моего кода FooBar, причем посты были добавлены независимо.

почему исключений надо бояться - тоже

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

public void someInternalFunc() {
  try {
    ...
    someSubFunc();
  } catch(Throwable e) {
    ...
    // здесь ты конечно забудешь добавить throw new UserInterruptException(), а даже если и не забудешь, это будет загромождать код и выглядеть отстойно
  }
}

private void someSunFunc() {
   ...
   if (...)
      throw new UserInterruptException()
}

Как думаешь, что проийзодет в твоём коде ниже, когда someInternalFunc проглотит твой UserInterruptException:

    class InterThread extends Thread {
        public void run() {
            try {
                Object v = eval(globalEnv, lv);
                cout(true, v.toString());
            } catch (Throwable e) {
                cout(true, e.toString());
                Thread.currentThread().interrupt();
            }
            if (threadListModel.contains(this))
                threadListModel.remove(threadListModel.indexOf(this));
        }
    }
foror ★★★★ ()
Последнее исправление: foror (всего исправлений: 1)
Ответ на: комментарий от foror

Поэтому и создается переменная либо stop, либо running, по которой контролируется состояние треда. И код работающий в треде ориентируется по этой переменной, что пора закругляться. А если не умеешь выйти из рекурсии обычными return'ами (от этого у тебя и стеки переполняются в java), то тут только прокачивать скилл программера.

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

Могу лишь добавить, что у меня на Pascal тоже в своё время стеки переполнялись, но ничего за года два надрочился в программинге и стал пилить уже что-то более-менее )

foror ★★★★ ()
Последнее исправление: foror (всего исправлений: 1)
Ответ на: комментарий от foror

Кто этот авторитетный гуру? )

Их полно в инете, вот например один из http://www.skipy.ru/technics/exceptions.html

Я не говорю про Thread#stop

Возможно я сделал поспешный вывод

Код в студию

Зачем? Вы (надеюсь) умеете, я (надеюсь) тоже. Смысла тестировать поток мне только нет, я же в первом посте написал, что хочу коллбэки а не постоянные проверки по таймеру или кнопке.

Банально, где-то в будущем поставишь обработку исключений, где-то на одной из вложенной функции

Да, есть такая буква. Надо знать, что нельзя нигде внутри перехватывать и не пробрасывать дальше исключения. Но это плата за простоту концепции.

Поэтому и создается переменная либо stop, либо running, по которой контролируется состояние треда.

А разве флаг isinterrupted не является такой переменной потока,уже встроенной?

А если не умеешь выйти из рекурсии обычными return'ами (от этого у тебя и стеки переполняются в java), то тут только прокачивать скилл программера.

Я пребывал в заблуждении, что по эксепшену все равно поток закроется и его стек освободится - какая разница как выходить? А стеки у меня переполняются потому что Java убогий язык, и мне еще придется осваивать костыли переноса вычислений со стека в кучу - трамплины или другие варианты. В нормальных языках такие костыли не требуются.

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

Я не претендую на звание профессионала (тем более в глазах тех, от кого не зависит моя зарплата )))), и я за goto в холиварах на его тему ).

Ivana ()
Ответ на: комментарий от Ivana

А разве флаг isinterrupted не является такой переменной потока,уже встроенной?

Можно и его использовать, но через interrupt() запарывается все IO. Если тред где-то что-то пишет на диск или читает какие-то данные для передачи в другой тред. То его можно тупо стопарнуть и получить подвешеное состояние.

Поэтому лучше использовать переменную, на которую код треда будет ориентироваться. И в моем коде FooBar#stop конечно ненужно вот так сразу делать thread.interrupt() после running.set(false).

Т.е. по хорошему сначала останавливаем по состоянию переменной. Затем, если прошло время, когда пора и честь знать, а тред все еще isalive, то стопим через interrupt().

Я пребывал в заблуждении, что по эксепшену все равно поток закроется и его стек освободится - какая разница как выходить?

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

осваивать костыли переноса вычислений со стека в кучу

Не надо осваивать. Нужно либо попробовать развернуть рекурсию в плоский код, либо подкрутить флаги JVM, увеличив размер стека до нужного размера:

java -Xss 4M

По умолчанию оно вроде 1Mb выставляет стек.

foror ★★★★ ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.