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



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



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



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

655   11  

11 ответов:

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

  1. использовать нить.присоединяйтесь() в вашем основном потоке ждать в блокирующем режиме для каждого потока, чтобы завершить, или
  2. Регистрация нить.isAlive () в режиме опроса - обычно не рекомендуется-ждать, пока каждый поток не будет завершен, или
  3. неортодоксально, для каждого потока в вопросе, вызов setUncaughtExceptionHandler для вызова метода объекта, и запрограммируйте каждый поток, чтобы бросить неперехваченное исключение, когда оно завершится, или
  4. использовать блокировки или синхронизаторы или механизмы от java.утиль.одновременно или
  5. более ортодоксально, создайте слушателя в своем основном потоке, а затем запрограммируйте каждый из своих потоков, чтобы сообщить слушателю, что они завершены.

как реализовать идею #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 метод будет вызван с экземпляром потока, который только что завершен (или разбился).

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

решение с помощью 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 ())

метка 1 - n-й DownloadingThread входит в зал ожидания

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

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

ярлык 2 - n-й DownloadingThread закончил свою работу загрузки и входит в зал ожидания. Если он последний, т. е. уже number_of_downloading_threads ввели его, включая основной поток выполнения, основной поток продолжит свое выполнение только тогда, когда все остальные потоки закончат загрузку.

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

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

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

затем сделайте исполнителя, который будет выполнять ваши 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); строка там и в вашем исполнителе, установите поток.UncaughtExceptionHandler, чтобы отправить вас в этот метод интерфейса.

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

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

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

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

*  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 закончат.

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

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

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

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

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

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

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

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

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

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

1.ExecutorServiceinvokeAll() API

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

2.CountDownLatch за

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

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

3.ForkJoinPool или newWorkStealingPool() на исполнители другой способ

4.Перебрать все Future задачи от отправки на ExecutorService и проверить состояние с блокировкой вызова get() on Future объект

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

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

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

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

(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();
}

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

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

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

Comments

    Ничего не найдено.