2009-03-31 3 views
100

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

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

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

+1

Посмотрите на Java 5 [Барьер класс] (Http: //java.sun. com/j2se/1.5.0/docs/api/java/util/concurrent/CyclicBarrier.html) – Fortyrunner

ответ

198

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

  1. Использование Thread.join() в основном потоке ждать в блокирующей моде для каждой тему, чтобы закончить, или
  2. Проверить Thread.isAlive() в избирательной моде - - обычно не рекомендуется - дождаться завершения каждой нити или
  3. Unorthodox, для каждой интересующей темы, позвоните по телефону 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(); 
} 

, а затем каждый из ваших Threads продлит 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, что довольно просто сделать.

+0

Привет! Мне нравится последняя идея. Я должен реализовать слушателя для этого? Спасибо –

+5

Я обновил свой ответ, чтобы добавить пример кода. – Eddie

+2

, но используя этот подход, notitiyListeners вызывается внутри run(), поэтому он будет называться insisde thread, и дальнейшие вызовы также будут выполняться там, не так ли? –

4

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

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

+2

Обратите внимание, что isAlive возвращает false, если поток еще не запущен (даже если ваш собственный поток уже вызвал запуск на нем) , –

0

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

4

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

+0

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

2

Я предлагаю посмотреть на javadoc для класса Thread.

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

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

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

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

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

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

11

Решение с помощью 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 class 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 - п-й DownloadingThread входит в комнату ожидания

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

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

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

0

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

1

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

7

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

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

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

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

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, если это позволяет ваш проект.

1

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

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

1. ExecutorServiceinvokeAll() API

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

2. CountDownLatch

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

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

3. ForkJoinPool или newWorkStealingPool() в Executors другой путь

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

Посмотрите связанные с СЕ:

How to wait for a thread that spawns it's own thread?

Executors: How to synchronously wait until all tasks have finished if tasks are created recursively?

1

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

(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(); 
} 
Смежные вопросы