Jak sprawdzić, czy zakończyły się inne wątki?


126

Mam obiekt z metodą o nazwie StartDownload(), która uruchamia trzy wątki.

Jak mogę otrzymać powiadomienie o zakończeniu wykonywania każdego wątku?

Czy istnieje sposób, aby dowiedzieć się, czy jeden (lub cały) wątek jest zakończony lub nadal jest wykonywany?


1
Spójrz na zajęcia Java 5 Barrier
Fortyrunner

Odpowiedzi:


228

Można to zrobić na kilka sposobów:

  1. Użyj Thread.join () w swoim głównym wątku, aby czekać w sposób blokujący na zakończenie każdego wątku lub
  2. Sprawdź Thread.isAlive () w sposób odpytywania - ogólnie odradzane - aby poczekać, aż każdy wątek zostanie ukończony, lub
  3. Nieortodoksyjne, dla każdego rozpatrywanego wątku wywołaj setUncaughtExceptionHandler, aby wywołać metodę w obiekcie i zaprogramuj każdy wątek tak, aby po zakończeniu wyrzucał nieprzechwycony wyjątek lub
  4. Użyj blokad, synchronizatorów lub mechanizmów z java.util.concurrent lub
  5. Bardziej ortodoksyjnie, utwórz odbiornik w głównym wątku, a następnie zaprogramuj każdy z wątków, aby informował słuchacza, że ​​ukończył.

Jak wdrożyć Pomysł nr 5? Cóż, jednym ze sposobów jest najpierw utworzenie interfejsu:

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

następnie utwórz następującą klasę:

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

a wtedy każdy z twoich wątków rozszerzy się NotifyingThreadi zamiast implementować run(), zaimplementuje doRun(). Dlatego po zakończeniu automatycznie powiadomią każdego, kto czeka na powiadomienie.

Na koniec w swojej głównej klasie - tej, która uruchamia wszystkie wątki (lub przynajmniej obiekt oczekujący na powiadomienie) - zmodyfikuj tę klasę do implement ThreadCompleteListeneri natychmiast po utworzeniu każdego wątku dodaj się do listy słuchaczy:

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

następnie, gdy każdy wątek zostanie zamknięty, Twoja notifyOfThreadCompletemetoda zostanie wywołana z instancją Thread, która właśnie się zakończyła (lub uległa awarii).

Należy pamiętać, że lepiej byłoby implements Runnable, zamiast extends Threadna NotifyingThreadmiarę rozciągania wątek jest zwykle zniechęca w nowym kodem. Ale koduję na twoje pytanie. Jeśli zmienisz NotifyingThreadklasę do zaimplementowania Runnable, musisz zmienić część kodu, który zarządza wątkami, co jest dość proste.


Cześć!! Podoba mi się ostatni pomysł. Mam zaimplementować słuchacza, aby to zrobić? Dzięki
Ricardo Felgueiras

4
ale używając tego podejścia, notifiyListeners jest wywoływane wewnątrz run (), więc będzie nazywane insisde the thread i tam również będą wykonywane dalsze wywołania, czy nie w ten sposób?
Jordi Puigdellívol

1
@Eddie Jordi pytał, czy można wywołać notifymetodę nie w jej wnętrzu run, ale po niej.
Tomasz Dzięcielewski

1
Pytanie naprawdę brzmi: jak teraz wyłączyć dodatkowy wątek. Wiem, że to się skończyło, ale jak mogę teraz uzyskać dostęp do głównego wątku?
Patrick,

1
Czy ten wątek jest bezpieczny? Wygląda na to, że notifyListeners (a tym samym notifyOfThreadComplete) będzie wywoływane w ramach NotifyingThread, a nie w wątku, który utworzył sam Listener.
Aaron

13

Rozwiązanie wykorzystujące 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 - tworzona jest cykliczna bariera z liczbą stron równą liczbie wykonujących się wątków plus jeden dla głównego wątku wykonania (w którym wykonywany jest startDownload ())

etykieta 1 - n-ty DownloadingThread wchodzi do poczekalni

etykieta 3 - NUMBER_OF_DOWNLOADING_THREADS weszło do poczekalni. Główny wątek wykonania zwalnia je, aby rozpocząć wykonywanie zadań pobierania mniej więcej w tym samym czasie

etykieta 4 - do poczekalni wchodzi główny wątek wykonania. To jest najtrudniejsza do zrozumienia część kodu. Nie ma znaczenia, który wątek wejdzie do poczekalni po raz drugi. Ważne jest, aby każdy wątek wchodzący do pokoju jako ostatni zapewniał, że wszystkie inne wątki pobierania zakończyły swoje zadania pobierania.

etykieta 2 - n-ty DownloadingThread zakończył zadanie pobierania i wchodzi do poczekalni. Jeśli jest to ostatni, tj. Już NUMBER_OF_DOWNLOADING_THREADS weszło do niego, łącznie z głównym wątkiem wykonania, główny wątek będzie kontynuował wykonywanie dopiero po zakończeniu pobierania wszystkich pozostałych wątków.


9

Naprawdę powinieneś preferować rozwiązanie, które wykorzystuje java.util.concurrent. Znajdź i przeczytaj Josha Blocha i / lub Briana Goetza na ten temat.

Jeśli nie używasz java.util.concurrent.*bezpośrednio wątków i bierzesz za nie odpowiedzialność, prawdopodobnie powinieneś użyć funkcji, join()aby wiedzieć, kiedy wątek jest gotowy. Oto super prosty mechanizm wywołania zwrotnego. Najpierw rozszerz Runnableinterfejs, aby mieć wywołanie zwrotne:

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

Następnie stwórz Executor, który wykona twój runnable i oddzwoni, kiedy to się skończy.

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

}

Inną oczywistą rzeczą, którą można dodać do CallbackRunnableinterfejsu, jest sposób obsługi wszelkich wyjątków, więc może umieść tam public void uncaughtException(Throwable e);linię, a w module wykonawczym zainstaluj Thread.UncaughtExceptionHandler, aby wysłać cię do tej metody interfejsu.

Ale robienie tego wszystkiego naprawdę zaczyna pachnieć java.util.concurrent.Callable. Powinieneś naprawdę spojrzeć na użycie, java.util.concurrentjeśli twój projekt na to pozwala.


Jestem trochę niejasny, co zyskujesz dzięki temu mechanizmowi wywołania zwrotnego, a nie po prostu wywołując, runner.join()a potem dowolny kod, który chcesz po tym, ponieważ wiesz, że wątek się skończył. Czy po prostu możesz zdefiniować ten kod jako właściwość elementu wykonawczego, więc możesz mieć różne rzeczy dla różnych elementów wykonawczych?
Stephen

2
Tak, runner.join()to najbardziej bezpośredni sposób oczekiwania. Zakładałem, że OP nie chciał blokować swojego głównego wątku wywołującego, ponieważ prosił o „powiadamianie” o każdym pobieraniu, które mogło zakończyć się w dowolnej kolejności. To oferowało jeden ze sposobów asynchronicznego otrzymywania powiadomień.
broc.seib

4

Czy chcesz poczekać, aż skończą? Jeśli tak, użyj metody Join.

Istnieje również właściwość isAlive, jeśli chcesz ją tylko sprawdzić.


3
Zauważ, że isAlive zwraca wartość false, jeśli wątek jeszcze się nie rozpoczął (nawet jeśli Twój własny wątek już wywołał start na nim).
Tom Hawtin - tackline

@ TomHawtin-tackline czy jesteś tego całkiem pewien? Byłoby to sprzeczne z dokumentacją Java („Wątek żyje, jeśli został uruchomiony i jeszcze nie umarł” - docs.oracle.com/javase/6/docs/api/java/lang/… ). Byłoby to również sprzeczne z odpowiedziami tutaj ( stackoverflow.com/questions/17293304/ )
Stephen

@Stephen Dawno temu to napisałem, ale wydaje się, że to prawda. Wyobrażam sobie, że spowodowało to problemy innych ludzi, które były świeże w mojej pamięci dziewięć lat temu. Dokładnie to, co można zaobserwować, będzie zależało od implementacji. Mówisz Threaddo start, co robi wątek, ale wywołanie natychmiast wraca. isAlivepowinien być prostym testem flagowym, ale kiedy googlowałem, metoda była native.
Tom Hawtin - tackline

4

Możesz przesłuchać wystąpienie wątku za pomocą metody getState (), która zwraca wystąpienie wyliczenia Thread.State z jedną z następujących wartości:

*  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.

Jednak myślę, że lepszym rozwiązaniem byłoby posiadanie głównego wątku, który czeka na zakończenie trojga dzieci, a następnie master kontynuowałby wykonywanie, gdy pozostałe 3 skończą.


Czekanie na wyjście trojga dzieci może nie pasować do paradygmatu użytkowania. Jeśli jest to menedżer pobierania, może chcieć rozpocząć 15 pobrań i po prostu usunąć stan z paska stanu lub powiadomić użytkownika o zakończeniu pobierania, w którym to przypadku oddzwonienie będzie działać lepiej.
digitaljoel

3

Możesz również użyć Executorsobiektu do utworzenia puli wątków ExecutorService . Następnie użyj tej invokeAllmetody, aby uruchomić każdy z wątków i pobrać Futures. Spowoduje to zablokowanie, dopóki wszyscy nie zakończą wykonywania. Inną opcją byłoby wykonanie każdego z nich przy użyciu puli, a następnie wywołanie awaitTerminationbloku do momentu zakończenia wykonywania puli. Po prostu nie zapomnij wywołać shutdown() po zakończeniu dodawania zadań.


2

Wiele rzeczy zmieniło się w ciągu ostatnich 6 lat na froncie wielowątkowym.

Zamiast używać join() i blokować API, możesz użyć

1. ExecutorService invokeAll() API

Wykonuje podane zadania, zwracając listę kontraktów futures posiadających status i wyniki po wykonaniu wszystkich.

2. CountDownLatch

Pomoc w synchronizacji, która pozwala jednemu lub większej liczbie wątków czekać na zakończenie zestawu operacji wykonywanych w innych wątkach.

A CountDownLatchjest inicjowany z podaną liczbą. Metody await blokują się, dopóki bieżąca liczba nie osiągnie zera z powodu wywołań countDown()metody, po czym wszystkie oczekujące wątki są zwalniane, a kolejne wywołania await wracają natychmiast. Jest to jednorazowe zjawisko - licznika nie można zresetować. Jeśli potrzebujesz wersji, która resetuje licznik, rozważ użycie CyclicBarrier.

3. ForkJoinPool lub newWorkStealingPool()w Executorach jest w inny sposób

4.Powtarzaj wszystkie Futurezadania od momentu przesłania ExecutorServicei sprawdź status z get()włączonym blokowaniem połączeniaFuture obiekcie

Spójrz na powiązane pytania dotyczące SE:

Jak czekać na wątek, który odrodzi swój własny wątek?

Wykonawcy: jak synchronicznie czekać, aż wszystkie zadania zostaną zakończone, jeśli zadania są tworzone rekurencyjnie?


2

Proponuję zajrzeć do javadoc dla klasy Thread .

Masz wiele mechanizmów manipulacji wątkami.

  • Twój główny wątek może join()wykonywać trzy wątki szeregowo i nie będzie kontynuowany, dopóki wszystkie trzy nie zostaną zakończone.

  • Sonduj stan wątków utworzonych wątków w odstępach czasu.

  • Umieść wszystkie utworzone wątki w osobnym ThreadGroupi odpytaj activeCount()na ThreadGroupi poczekaj, aż osiągnie 0.

  • Skonfiguruj niestandardowy interfejs wywołania zwrotnego lub nasłuchiwania do komunikacji między wątkami.

Jestem pewien, że wciąż brakuje mi wielu innych sposobów.


1

Oto rozwiązanie, które jest proste, krótkie, łatwe do zrozumienia i doskonale dla mnie działa. Musiałem rysować na ekranie, gdy kończy się kolejny wątek; ale nie mógł, ponieważ główny wątek ma kontrolę nad ekranem. Więc:

(1) Utworzyłem zmienną globalną: boolean end1 = false; wątek ustawia ją na true po zakończeniu. Jest to odbierane w głównym wątku przez pętlę „postDelayed”, na której jest udzielana odpowiedź.

(2) Mój wątek zawiera:

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) Na szczęście „postDelayed” jest uruchamiany w głównym wątku, więc to jest miejsce, w którym co sekundę sprawdza inny wątek. Kiedy kończy się drugi wątek, może to rozpocząć się od tego, co chcemy robić dalej.

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) Na koniec zacznij całą operację gdzieś w kodzie, wywołując:

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

1

Myślę, że najłatwiej jest użyć ThreadPoolExecutorklasy.

  1. Ma kolejkę i możesz ustawić, ile wątków ma pracować równolegle.
  2. Ma ładne metody wywołania zwrotnego:

Metody hakowe

Ta klasa udostępnia chronione, które można zastąpić beforeExecute(java.lang.Thread, java.lang.Runnable)i afterExecute(java.lang.Runnable, java.lang.Throwable)metody, które są wywoływane przed i po wykonaniu każdego zadania. Można ich używać do manipulowania środowiskiem wykonawczym; na przykład ponowna inicjalizacja ThreadLocals, zbieranie statystyk lub dodawanie wpisów dziennika. Dodatkowo metodę terminated()można nadpisać, aby wykonać specjalne przetwarzanie, które musi zostać wykonane po całkowitym zakończeniu działania Executora.

czyli dokładnie to, czego potrzebujemy. Nadpisujemy, afterExecute()aby uzyskać wywołania zwrotne po zakończeniu każdego wątku i nadpisujemyterminated() aby wiedzieć, kiedy wszystkie wątki są zakończone.

Oto, co powinieneś zrobić

  1. Utwórz executor:

    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. I rozpocznij swoje wątki:

    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 
    }

Metoda wewnętrzna informUiThatWeAreDone(); zrób wszystko, co musisz zrobić, gdy wszystkie wątki zostaną ukończone, na przykład zaktualizuj interfejs użytkownika.

UWAGA: Nie zapomnij o używaniu synchronizedmetod, ponieważ wykonujesz swoją pracę równolegle i BĄDŹ BARDZO OSTROŻNY, jeśli zdecydujesz się wywołać synchronizedmetodę z innegosynchronized metody! To często prowadzi do impasu

Mam nadzieję że to pomoże!



0

Zapoznaj się z dokumentacją Java dotyczącą klasy Thread. Możesz sprawdzić stan wątku. Jeśli umieścisz trzy wątki w zmiennych składowych, wszystkie trzy wątki będą mogły czytać swoje stany.

Musisz jednak być trochę ostrożny, ponieważ możesz spowodować wyścigi między wątkami. Po prostu staraj się unikać skomplikowanej logiki opartej na stanie innych wątków. Zdecydowanie unikaj zapisywania wielu wątków do tych samych zmiennych.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.