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

Как узнать, закончились ли другие потоки?

У меня есть объект с методом с именем StartDownload(), который запускает три потока.

Как получить уведомление, когда каждый поток завершил выполнение?

Есть ли способ узнать, закончен или все еще выполняется один (или все) поток?

4b9b3361

Ответ 1

Существует несколько способов сделать это:

  • Используйте Thread.join() в своем основном потоке, чтобы ждать блокирующим способом для каждого потока для завершения или
  • Проверьте Thread.isAlive() в режиме опроса - как правило, не рекомендуется - дождаться завершения каждого потока или
  • Unorthodox, для каждой обсуждаемой темы, вызовите setUncaughtExceptionHandler, чтобы вызвать метод в вашем объекте и запрограммировать каждый поток, чтобы выкинуть неотобравшийся Исключение при завершении или
  • Используйте блокировки или синхронизаторы или механизмы из java.util.concurrent или
  • Больше ортодоксов, создайте слушателя в главной теме, а затем запрограммируйте каждый из ваших потоков, чтобы сообщить слушателю, что они были завершены.

Как реализовать идею №5? Итак, одним из способов является создание интерфейса:

public interface ThreadCompleteListener {
    void notifyOfThreadComplete(final Thread thread);
}

затем создайте следующий класс:

public abstract class NotifyingThread extends Thread {
  private final Set<ThreadCompleteListener> listeners
                   = new CopyOnWriteArraySet<ThreadCompleteListener>();
  public final void addListener(final ThreadCompleteListener listener) {
    listeners.add(listener);
  }
  public final void removeListener(final ThreadCompleteListener listener) {
    listeners.remove(listener);
  }
  private final void notifyListeners() {
    for (ThreadCompleteListener listener : listeners) {
      listener.notifyOfThreadComplete(this);
    }
  }
  @Override
  public final void run() {
    try {
      doRun();
    } finally {
      notifyListeners();
    }
  }
  public abstract void doRun();
}

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

Наконец, в вашем основном классе - том, который запускает все потоки (или, по крайней мере, объект, ожидающий уведомления), - измените этот класс на implement ThreadCompleteListener и сразу после создания каждого потока добавьте себя в список слушателей

NotifyingThread thread1 = new OneOfYourThreads();
thread1.addListener(this); // add ourselves as a listener
thread1.start();           // Start the Thread

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

Обратите внимание, что лучше было бы implements Runnable, а не extends Thread для NotifyingThread, поскольку расширение потока обычно не рекомендуется в новом коде. Но я кодирую ваш вопрос. Если вы измените класс NotifyingThread для реализации Runnable, тогда вам придется изменить часть своего кода, который управляет Threads, что довольно просто сделать.

Ответ 2

Решение с использованием CyclicBarrier

public class Downloader {
  private CyclicBarrier barrier;
  private final static int NUMBER_OF_DOWNLOADING_THREADS;

  private DownloadingThread extends Thread {
    private final String url;
    public DownloadingThread(String url) {
      super();
      this.url = url;
    }
    @Override
    public void run() {
      barrier.await(); // label1
      download(url);
      barrier.await(); // label2
    }
  }
  public void startDownload() {
    // plus one for the main thread of execution
    barrier = new CyclicBarrier(NUMBER_OF_DOWNLOADING_THREADS + 1); // label0
    for (int i = 0; i < NUMBER_OF_DOWNLOADING_THREADS; i++) {
      new DownloadingThread("http://www.flickr.com/someUser/pic" + i + ".jpg").start();
    }
    barrier.await(); // label3
    displayMessage("Please wait...");
    barrier.await(); // label4
    displayMessage("Finished");
  }
}

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

label 1 - n-й DownloadThread входит в зал ожидания

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

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

label 2 - n-й DownloadThread завершил загрузку и входит в комнату ожидания. Если он последний, т.е. уже введен NUMBER_OF_DOWNLOADING_THREADS, включая основной поток выполнения, основной поток продолжит выполнение только тогда, когда все остальные потоки завершили загрузку.

Ответ 3

Вы действительно должны предпочесть решение, которое использует java.util.concurrent. Найдите и прочитайте Джоша Блоха и/или Брайана Гетца по этой теме.

Если вы не используете java.util.concurrent.* и несете ответственность за использование потоков напрямую, то вам, вероятно, следует использовать join(), чтобы знать, когда поток выполняется. Вот супер простой механизм обратного вызова. Сначала расширьте интерфейс Runnable, чтобы иметь обратный вызов:

public interface CallbackRunnable extends Runnable {
    public void callback();
}

Затем создайте Executor, который выполнит вашу runnable и перезвонит вам, когда это будет сделано.

public class CallbackExecutor implements Executor {

    @Override
    public void execute(final Runnable r) {
        final Thread runner = new Thread(r);
        runner.start();
        if ( r instanceof CallbackRunnable ) {
            // create a thread to perform the callback
            Thread callerbacker = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        // block until the running thread is done
                        runner.join();
                        ((CallbackRunnable)r).callback();
                    }
                    catch ( InterruptedException e ) {
                        // someone doesn't want us running. ok, maybe we give up.
                    }
                }
            });
            callerbacker.start();
        }
    }

}

Другой вид очевидной вещи для добавления в ваш интерфейс CallbackRunnable - это средство для обработки любых исключений, поэтому, возможно, поместите строку public void uncaughtException(Throwable e); там и в вашем исполнителе, установите Thread.UncaughtExceptionHandler, чтобы отправить вам этот метод интерфейса.

Но все, что действительно начинает пахнуть, как java.util.concurrent.Callable. Вы действительно должны смотреть на использование java.util.concurrent, если это позволяет ваш проект.

Ответ 4

Вы хотите дождаться их завершения? Если это так, используйте метод Join.

Существует также свойство isAlive, если вы просто хотите его проверить.

Ответ 5

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

*  NEW
  A thread that has not yet started is in this state.
* RUNNABLE
  A thread executing in the Java virtual machine is in this state.
* BLOCKED
  A thread that is blocked waiting for a monitor lock is in this state.
* WAITING
  A thread that is waiting indefinitely for another thread to perform a particular action is in this state.
* TIMED_WAITING
  A thread that is waiting for another thread to perform an action for up to a specified waiting time is in this state.
* TERMINATED
  A thread that has exited is in this state.

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

Ответ 6

Я бы предложил посмотреть на javadoc для Thread class.

У вас есть несколько механизмов для обработки потоков.

  • Ваш основной поток мог бы join() три потока поочередно, и тогда не будет продолжаться, пока не будут выполнены все три.

  • Опросите состояние потока порожденных потоков с интервалами.

  • Поместите все порожденные потоки в отдельный ThreadGroup и опросите activeCount() в ThreadGroup и дождитесь, пока он достигнет 0.

  • Настройка пользовательского интерфейса обратного вызова или прослушивателя для межпоточной связи.

Я уверен, что есть много других способов, которые я все еще не хватает.

Ответ 7

Вы также можете использовать объект Executors для создания пула потоков ExecutorService. Затем используйте метод invokeAll для запуска каждого из ваших потоков и получения фьючерсов. Это будет заблокировано, пока все не завершит выполнение. Другим вариантом будет выполнение каждого из них с использованием пула, а затем вызов awaitTermination для блокировки до завершения пула. Просто не забудьте вызвать shutdown(), когда вы закончите добавлять задачи.

Ответ 8

Многие вещи были изменены за последние 6 лет на многопоточном фронте.

Вместо использования join() и блокировки API вы можете использовать

1. ExecutorService invokeAll() API

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

2. CountDownLatch

Помощник синхронизации, позволяющий одному или нескольким потокам ждать завершения набора операций в других потоках.

A CountDownLatch инициализируется данным счетчиком. Методы ожидания выполняются до тех пор, пока текущий счетчик не достигнет нуля из-за вызовов метода countDown(), после чего все ожидающие потоки будут освобождены, и любые последующие вызовы ждут немедленно. Это одноразовое явление - счет не может быть reset. Если вам нужна версия, которая сбрасывает счетчик, рассмотрите возможность использования CyclicBarrier.

3. ForkJoinPool или newWorkStealingPool() в Executors - это другой способ

4. Запустите все задачи Future из submit на ExecutorService и проверьте состояние с помощью блокирующего вызова get() на Future object

Посмотрите на связанные вопросы SE:

Как подождать поток, который порождает собственный поток?

Исполнители: как синхронно ждать завершения всех задач, если задачи создаются рекурсивно?

Ответ 9

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

(1) Я создал глобальную переменную: boolean end1 = false; Поток устанавливает значение true при завершении. Это подхвачено в mainthread посредством цикла postDelayed, на который он отвечает.

(2) Мой поток содержит:

void myThread() {
    end1 = false;
    new CountDownTimer(((60000, 1000) { // milliseconds for onFinish, onTick
        public void onFinish()
        {
            // do stuff here once at end of time.
            end1 = true; // signal that the thread has ended.
        }
        public void onTick(long millisUntilFinished)
        {
          // do stuff here repeatedly.
        }
    }.start();

}

(3) К счастью, "postDelayed" запускается в основном потоке, так что, когда проверяется другой поток один раз в секунду. Когда закончится другой поток, это может начать все, что мы хотим сделать дальше.

Handler h1 = new Handler();

private void checkThread() {
   h1.postDelayed(new Runnable() {
      public void run() {
         if (end1)
            // resond to the second thread ending here.
         else
            h1.postDelayed(this, 1000);
      }
   }, 1000);
}

(4) Наконец, запустите все, что работает где-то в вашем коде, вызвав:

void startThread()
{
   myThread();
   checkThread();
}

Ответ 10

Посмотрите на документацию Java для класса Thread. Вы можете проверить состояние потока. Если вы помещаете три потока в переменные-члены, то все три потока могут считывать состояния друг друга.

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

Ответ 11

Вы также можете использовать SwingWorker, который имеет встроенную поддержку изменения свойств. См. addPropertyChangeListener() или get() метод для примера прослушивания изменения состояния.

Ответ 12

NotificationThread thread1 = new OneOfYourThreads();

Эта строка не работает для меня. На месте "OneOfYourThreads()" я создал имя моего потока, но они сказали мне изменить тип моего потока на NotificationThread.