Как быстро и чисто прервать поток в Java? - PullRequest
28 голосов
/ 18 сентября 2008

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

Как я могу сделать что-то подобное?

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

Ответы [ 15 ]

12 голосов
/ 18 сентября 2008

Попробуйте interrupt (), как говорили некоторые, чтобы увидеть, имеет ли это какое-то значение для вашего потока. Если нет, попробуйте уничтожить или закрыть ресурс, который остановит поток. Это имеет шанс быть немного лучше, чем пытаться бросить в него Thread.stop ().

Если производительность приемлема, вы можете рассматривать каждое 3D-обновление как отдельное непрерывное событие и просто доводить его до конца, а затем проверять наличие нового обновления. Это может сделать GUI немного нестабильным для пользователей, так как они смогут внести пять изменений, затем увидеть графические результаты того, как вещи были пять изменений назад, а затем увидеть результат их последнего изменения. Но в зависимости от того, как долго этот процесс, он может быть терпимым, и это позволит избежать необходимости уничтожать поток. Дизайн может выглядеть так:

boolean stopFlag = false;
Object[] latestArgs = null;

public void run() {
  while (!stopFlag) {
    if (latestArgs != null) {
      Object[] args = latestArgs;
      latestArgs = null;
      perform3dUpdate(args);
    } else {
      Thread.sleep(500);
    }
  }
}

public void endThread() {
  stopFlag = true;
}

public void updateSettings(Object[] args) {
  latestArgs = args;
}
8 голосов
/ 18 сентября 2008

Поток, который обновляет трехмерный вид, должен периодически проверять какой-либо флаг (используйте volatile boolean), чтобы увидеть, должен ли он завершиться. Если вы хотите прервать поток, просто установите флаг. Когда поток в следующий раз проверяет флаг, он должен просто выйти из цикла, который он использует для обновления представления и возврата из метода run.

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

Если он выполняется в течение длительного периода времени, и вы просто должны прекратить его, вы можете рассмотреть использование устаревшего метода Thread.stop(). Тем не менее, это не рекомендуется по уважительной причине. Если этот поток остановлен во время выполнения какой-либо операции, которая оставляет что-либо в несогласованном состоянии или какой-либо ресурс не был очищен должным образом, то у вас могут быть проблемы. Вот примечание из документации :

Этот метод небезопасен. Остановка потока с Thread.stop заставляет его разблокировать все контролирует, что он заблокирован (как естественное следствие непроверенного Исключение ThreadDeath распространяется вверх стек). Если какой-либо из объектов ранее защищены этими мониторами были в противоречивом состоянии, поврежденные объекты становятся видимыми для другие темы, потенциально в результате в произвольном поведении. Многие использования Стоп должен быть заменен кодом, который просто изменяет некоторую переменную указать, что целевой поток должен прекратить бег. Целевой поток должен регулярно проверяйте эту переменную и вернуться из его метода запуска в упорядоченная мода, если переменная указывает на то, что он должен остановиться. Если целевой поток ждет долго периоды (по условной переменной, для пример), метод прерывания должен использоваться, чтобы прервать ожидание. За больше информации, см. Почему Thread.stop, Thread.suspend и Thread.resume Устаревший?

4 голосов
/ 18 сентября 2008

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

Наружная резьба:

if(oldThread.isRunning())
{
    oldThread.interrupt();
    // Be careful if you're doing this in response to a user
    // action on the Event Thread
    // Blocking the Event Dispatch Thread in Java is BAD BAD BAD
    oldThread.join();
}

oldThread = new Thread(someRunnable);
oldThread.start();

Внутренний работоспособный / резьба:

public void run()
{
    // If this is all you're doing, interrupts and boolean flags may not work
    callExternalMethod(args);
}

public void run()
{
    while(!Thread.currentThread().isInterrupted)
    {
        // If you have multiple steps in here, check interrupted peridically and
        // abort the while loop cleanly
    }
}
3 голосов
/ 18 сентября 2008

Разве это не похоже на вопрос "Как я могу прервать поток, если нет другого метода, кроме Thread.stop ()?"

Очевидно, единственный верный ответ - Thread.stop (). Это некрасиво, может привести к поломке в некоторых обстоятельствах, может привести к утечке памяти / ресурсов, и TLEJD (Лига выдающихся разработчиков Java) не одобряет его, однако в некоторых случаях, подобных этому, он все еще может быть полезен На самом деле нет другого метода, если в стороннем коде нет доступного метода close.

OTOH, иногда существуют закрытые методы. То есть закрытие базового потока, с которым он работает, или какого-либо другого ресурса, который ему необходим для выполнения своей работы. Однако это редко лучше, чем просто вызов Thread.stop () и предоставление ему возможности ThreadDeathException.

2 голосов
/ 27 октября 2008

Принятый ответ на этот вопрос позволяет отправить пакетную работу в фоновую ветку. Это может быть лучше для этого:

public abstract class dispatcher<T> extends Thread {

  protected abstract void processItem(T work);

  private List<T> workItems = new ArrayList<T>();
  private boolean stopping = false;
  public void submit(T work) {
    synchronized(workItems) {
      workItems.add(work);
      workItems.notify();
    }
  }
  public void exit() {
    stopping = true;
    synchronized(workItems) {
      workItems.notifyAll();
    }
    this.join();
  }
  public void run() {
    while(!stopping) {
      T work;
      synchronized(workItems) {
        if (workItems.empty()) {
            workItems.wait();
            continue;
        }
        work = workItems.remove(0);
      }
      this.processItem(work);
    }
  }
}

Чтобы использовать этот класс, расширьте его, предоставив тип для T и реализацию processItem (). Затем просто создайте его и вызовите start () для него.

Возможно, вы захотите добавить метод abortPending:

public void abortPending() {
  synchronized(workItems) {
    workItems.clear();
  }
}

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

1 голос
/ 20 сентября 2008

Решения, которые предполагают использование логического поля, являются правильным направлением. Но поле должно быть изменчивым. Спецификация языка Java говорит :

"Например, в следующем (неработающем) фрагменте кода предположим, что this.done не является энергозависимое логическое поле:

while (!this.done)
  Thread.sleep(1000);

Компилятор может прочитать поле this.done только один раз и повторно использовать кэшированное значение при каждом выполнении цикла. Это будет означать, что цикл никогда не завершится, даже если другой поток изменил значение this.done. "

Насколько я помню "Параллелизм Java в Pratice" имеет целью использовать interrupt () и interrupted () методы java.lang.Thread .

1 голос
/ 18 сентября 2008

Я предлагаю вам просто запретить использование нескольких потоков, используя ожидание и уведомление, чтобы, если пользователь изменяет значение много раз, он запускал поток только один раз. Если пользователь изменяет значение 10 раз, он будет запускать поток при первом изменении, а затем все изменения, сделанные до создания потока, будут «свернуты» в одно уведомление. Это не остановит поток, но нет хороших способов сделать это, основываясь на вашем описании.

1 голос
/ 18 сентября 2008

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

1 голос
/ 18 сентября 2008

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

1 голос
/ 18 сентября 2008

Поток завершится после того, как метод run() будет завершен, поэтому вам нужна некоторая проверка, которая завершит метод.

Вы можете прервать поток, а затем выполнить некоторую проверку, которая будет периодически проверять isInterrupted() и возвращаться из метода run().

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

static boolean shouldExit = false;
Thread t = new Thread(new Runnable() {
    public void run() {
        while (!shouldExit) {
            // do stuff
        }
    }
}).start();
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...