Как узнать, закончили ли другие темы? - PullRequest
119 голосов
/ 31 марта 2009

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

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

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

Ответы [ 12 ]

220 голосов
/ 31 марта 2009

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

  1. Используйте Thread.join () в вашем основном потоке, чтобы блокировать ожидание завершения каждого потока, или
  2. Проверьте Thread.isAlive () способом опроса - как правило, не рекомендуется - ждать, пока не завершится каждый поток, или
  3. Неортодоксально, для каждого рассматриваемого потока, вызовите setUncaughtExceptionHandler , чтобы вызвать метод в вашем объекте, и запрограммируйте каждый поток, чтобы выбрасывать необработанное исключение, когда оно завершается, или
  4. Использовать блокировки или синхронизаторы или механизмы из java.util.concurrent или
  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, вам придется изменить часть кода, который управляет потоками, что довольно просто сделать.

13 голосов
/ 23 июня 2009

Решение с использованием 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 - основной поток исполнения входит в зал ожидания. Это самая сложная часть кода для понимания. Неважно, какая нить войдет в зал ожидания во второй раз. Важно, чтобы любой поток, который входит в комнату в последний раз, гарантировал, что все остальные потоки загрузки закончили свои задания загрузки.

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

8 голосов
/ 26 февраля 2013

Вы должны действительно предпочесть решение, которое использует 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 голосов
/ 31 марта 2009

Вы можете запросить экземпляр потока с помощью 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.

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

4 голосов
/ 31 марта 2009

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

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

2 голосов
/ 31 марта 2009

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

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

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

  • Опрашивать состояние резьбы порожденных нитей с интервалами.

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

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

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

1 голос
/ 28 февраля 2019

Полагаю, самый простой способ - использовать класс ThreadPoolExecutor.

  1. У него есть очередь, и вы можете указать, сколько потоков должно работать параллельно.
  2. Имеет хорошие методы обратного вызова:

Методы крюка

Этот класс предоставляет защищенные переопределяемые beforeExecute(java.lang.Thread, java.lang.Runnable) и afterExecute(java.lang.Runnable, java.lang.Throwable) методы, которые вызываются до и после выполнения каждой задачи. Их можно использовать для манипулирования средой исполнения; например, повторная инициализация ThreadLocals, сбор статистики или добавление записей журнала. Кроме того, метод terminated() может быть переопределен для выполнения любой специальной обработки, которую необходимо выполнить после полного завершения Исполнителя.

что именно то, что нам нужно. Мы переопределим afterExecute(), чтобы получить обратные вызовы после завершения каждого потока, и переопределим terminated(), чтобы узнать, когда все потоки завершены.

Так вот что вы должны сделать

  1. Создать исполнителя:

    private ThreadPoolExecutor executor;
    private int NUMBER_OF_CORES = Runtime.getRuntime().availableProcessors();    
    
    
    
    private void initExecutor() {
    
    executor = new ThreadPoolExecutor(
            NUMBER_OF_CORES * 2,  //core pool size
            NUMBER_OF_CORES * 2, //max pool size
            60L, //keep aive time
            TimeUnit.SECONDS,
            new LinkedBlockingQueue<Runnable>()
    ) {
    
        @Override
        protected void afterExecute(Runnable r, Throwable t) {
            super.afterExecute(r, t);
                //Yet another thread is finished:
                informUiAboutProgress(executor.getCompletedTaskCount(), listOfUrisToProcess.size());
            }
        }
    
    };
    
        @Override
        protected void terminated() {
            super.terminated();
            informUiThatWeAreDone();
        }
    
    }
    
  2. И начните свои темы:

    private void startTheWork(){
        for (Uri uri : listOfUrisToProcess) {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    doSomeHeavyWork(uri);
                }
            });
        }
        executor.shutdown(); //call it when you won't add jobs anymore 
    }
    

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

ПРИМЕЧАНИЕ: Не забывайте об использовании методов synchronized, так как вы выполняете свою работу параллельно и БУДЕТЕ ОЧЕНЬ ОСТОРОЖНЫ, если решите вызвать метод synchronized из другого метода synchronized! Это часто приводит к тупикам

Надеюсь, это поможет!

1 голос
/ 06 октября 2016

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

(1) Я создал глобальную переменную: boolean end1 = false; Поток устанавливает ее в true при завершении. Это попадает в основную ветку с помощью цикла 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();
}
1 голос
/ 21 февраля 2016

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

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

1. ExecutorService invokeAll() API

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

2. CountDownLatch

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

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

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

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

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

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

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

1 голос
/ 21 февраля 2011

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

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