Czy dwukrotne wywołanie metody start w tym samym wątku jest legalne?


90

Poniższy kod prowadzi do java.lang.IllegalThreadStateException: Thread already startedsytuacji, gdy wywołałem start()metodę po raz drugi w programie.

updateUI.join();    

if (!updateUI.isAlive()) 
    updateUI.start();

Dzieje się to za drugim razem updateUI.start(). Przechodziłem przez to wiele razy, a wątek jest wywoływany i całkowicie biegnie do końca przed uderzeniem updateUI.start().

Wywołanie updateUI.run()pozwala uniknąć błędu, ale powoduje, że wątek działa w wątku interfejsu użytkownika (wątku wywołującym, jak wspomniano w innych postach na SO), co nie jest tym, czego chcę.

Czy wątek można uruchomić tylko raz? Jeśli tak, to co mam zrobić, jeśli chcę ponownie uruchomić wątek? Ten konkretny wątek wykonuje obliczenia w tle, jeśli nie robię tego w wątku, to jest to robione w wątku interfejsu użytkownika, a użytkownik ma nieuzasadnione długie oczekiwanie.


9
Dlaczego po prostu nie przeczytałeś javadoc - jasno opisuje to kontrakt.
mP.

Odpowiedzi:


112

Ze specyfikacji interfejsu API języka Java dla Thread.startmetody:

Otwarcie wątku więcej niż jeden raz nie jest legalne. W szczególności wątek nie może zostać ponownie uruchomiony po zakończeniu wykonywania.

Ponadto:

Zgłasza:
IllegalThreadStateException- jeśli wątek został już uruchomiony.

Więc tak, a Threadmożna uruchomić tylko raz.

Jeśli tak, to co mam zrobić, jeśli chcę ponownie uruchomić wątek?

Jeśli a Threadtrzeba uruchomić więcej niż jeden raz, należy utworzyć nową instancję Threadi wywołać startją.


Dzięki. Sprawdziłem dokumentację w IDE i samouczku Java pod kątem wątków (i Google też). W przyszłości sprawdzę specyfikację API. To krytyczne „… nigdy nie wolno zaczynać więcej niż raz…” nie występuje w innych czytaniach.
Będzie

@coobird, jeśli przypiszę nazwę starego obiektu wątku do nowego Thread (), po zakończeniu starego wątku, czy stary wątek zostanie wyrzucony jako śmieci (tj. czy jest automatycznie odzyskiwany, czy musi to być zrobione jawnie)?
snapfractalpop

Będzie zbierany jako śmieci, dopóki wątek nie będzie już uruchomiony.
odlot

1
Ta odpowiedź jest nieco nieaktualna. Jeśli nowoczesny program Java musi wykonać zadanie więcej niż jeden raz, nie powinien za Threadkażdym razem tworzyć nowego . Zamiast tego powinien przesłać zadanie do puli wątków (np. java.util.concurrent.ThreadPoolExecutor)
Solomon Slow

13

Dokładnie tak. Z dokumentacji :

Otwarcie wątku więcej niż jeden raz nie jest legalne. W szczególności wątek nie może zostać ponownie uruchomiony po zakończeniu wykonywania.

Jeśli chodzi o to, co możesz zrobić dla powtarzających się obliczeń, wydaje się, że możesz użyć metody InvokeLater SwingUtilities . Już eksperymentujesz z dzwonieniem run()bezpośrednio, co oznacza, że ​​już myślisz o użyciu Runnableraczej niż surowego Thread. Spróbuj zastosować tę invokeLatermetodę tylko do Runnablezadania i zobacz, czy lepiej pasuje to do Twojego schematu myślowego.

Oto przykład z dokumentacji:

 Runnable doHelloWorld = new Runnable() {
     public void run() {
         // Put your UI update computations in here.
         // BTW - remember to restrict Swing calls to the AWT Event thread.
         System.out.println("Hello World on " + Thread.currentThread());
     }
 };

 SwingUtilities.invokeLater(doHelloWorld);
 System.out.println("This might well be displayed before the other message.");

Jeśli zastąpisz to printlnwywołanie swoimi obliczeniami, może to być dokładnie to, czego potrzebujesz.

EDYCJA: podążając za komentarzem, nie zauważyłem tagu Androida w oryginalnym poście. Odpowiednikiem invokeLater w pracy systemu Android jest Handler.post(Runnable). Z jego javadoc:

/**
 * Causes the Runnable r to be added to the message queue.
 * The runnable will be run on the thread to which this handler is
 * attached.
 *
 * @param r The Runnable that will be executed.
 *
 * @return Returns true if the Runnable was successfully placed in to the
 *         message queue.  Returns false on failure, usually because the
 *         looper processing the message queue is exiting.
 */

Tak więc w świecie Androida możesz użyć tego samego przykładu, co powyżej, zastępując Swingutilities.invokeLaterodpowiedni post na Handler.


OP pyta o wątkowanie w systemie Android, które nie obejmuje SwingUtilities.
Austyn Mahoney

@Austyn, masz rację. Dodałem uwagi o Handler.post (), aby zilustrować równoległy kod Androida.
Bob Cross

1
Innym sposobem, jeśli próbujesz tylko zaktualizować swój interfejs użytkownika, jest użycie RunOnUIThread(Runnable)lub View.post(Runnable)zamiast tworzenia własnego programu obsługi. Będą one uruchamiać plik runnable w głównym wątku, umożliwiając aktualizację interfejsu użytkownika.
Austyn Mahoney

3

Właśnie otrzymana odpowiedź wyjaśnia, dlaczego nie powinieneś robić tego, co robisz. Oto kilka opcji rozwiązania rzeczywistego problemu.

Ten konkretny wątek wykonuje obliczenia w tle, jeśli nie zrobię tego w wątku, niż w wątku interfejsu użytkownika, a użytkownik ma nieuzasadnione długie oczekiwanie.

Zrzuć swój własny wątek i użyj AsyncTask.

Lub utwórz nowy wątek, kiedy go potrzebujesz.

Lub skonfiguruj swój wątek tak, aby działał poza kolejką roboczą (np. LinkedBlockingQueue), Zamiast restartować wątek.


3

Nie , nie możemy ponownie uruchomić Thread, spowoduje to wystąpienie wyjątku runtimeException java.lang.IllegalThreadStateException. >

Powodem jest to, że po wykonaniu metody run () przez Thread przechodzi w stan martwy.

Weźmy przykład - myślenie o ponownym uruchomieniu wątku i wywołaniu na nim metody start () (która wewnętrznie wywoła metodę run ()) jest dla nas czymś, co przypomina proszenie martwego człowieka o obudzenie się i uruchomienie. Ponieważ po zakończeniu życia osoba przechodzi w stan martwy.

public class MyClass implements Runnable{

    @Override
    public void run() {
           System.out.println("in run() method, method completed.");
    }

    public static void main(String[] args) {
                  MyClass obj=new MyClass();            
        Thread thread1=new Thread(obj,"Thread-1");
        thread1.start();
        thread1.start(); //will throw java.lang.IllegalThreadStateException at runtime
    }

}

/ * OUTPUT w metodzie run (), metoda zakończona. Wyjątek w wątku „main” java.lang.IllegalThreadStateException at java.lang.Thread.start (nieznane źródło) * /

Sprawdź to


2

Co powinieneś zrobić, to stworzyć Runnable i owijać go nowym wątkiem za każdym razem, gdy chcesz uruchomić Runnable. Byłoby to naprawdę brzydkie, ale możesz owinąć wątek innym wątkiem, aby ponownie uruchomić kod, ale zrób to tylko wtedy, gdy naprawdę musisz.


jakikolwiek snipet, jak owinąć?
Vinay

1

Jak powiedziałeś, wątku nie można rozpocząć więcej niż jeden raz.

Prosto z paszczy konia: Java API Spec

Otwarcie wątku więcej niż jeden raz nie jest legalne. W szczególności wątek nie może zostać ponownie uruchomiony po zakończeniu wykonywania.

Jeśli chcesz ponownie uruchomić wszystko, co dzieje się w twoim wątku, będziesz musiał utworzyć nowy wątek i uruchomić go.


0

Ponowne użycie wątku jest nielegalną czynnością w Java API. Możesz jednak umieścić go w działającym narzędziu i ponownie uruchomić tę instancję.


0

Tak, nie możemy już uruchomić wątku. Wyrzuci wyjątek IllegalThreadStateException w czasie wykonywania - jeśli wątek został już uruchomiony.

Co jeśli naprawdę potrzebujesz uruchomić wątek: Opcja 1) Jeśli wątek musi być uruchomiony więcej niż raz, należy utworzyć nową instancję wątku i wywołać start na nim.


0

Czy wątek można uruchomić tylko raz?

Tak. Możesz zacząć to dokładnie raz.

Jeśli tak, to co mam zrobić, jeśli chcę ponownie uruchomić wątek? Ten konkretny wątek wykonuje obliczenia w tle, jeśli nie robię tego w wątku, to jest to robione w wątku interfejsu użytkownika, a użytkownik ma nieracjonalnie długie oczekiwanie.

Nie uruchamiaj Threadponownie. Zamiast tego utwórz Runnable i umieść go w Handler of HandlerThread . Możesz przesłać wiele Runnableobiektów. Jeśli chcesz wysłać dane z powrotem do wątku UI, ze w swojej Runnable run()metodzie, umieścić Messagena Handlerz wątku UI i proceshandleMessage

Zobacz przykładowy kod w tym poście:

Android: Toast w wątku


0

Nie wiem, czy jest to dobra praktyka, ale kiedy pozwolę wywołać run () wewnątrz metody run (), nie zgłasza błędu i faktycznie robi dokładnie to, co chciałem.

Wiem, że to nie jest ponowne rozpoczęcie wątku, ale może ci się to przyda.

public void run() {

    LifeCycleComponent lifeCycleComponent = new LifeCycleComponent();

    try {
        NetworkState firstState = lifeCycleComponent.getCurrentNetworkState();
        Thread.sleep(5000);
        if (firstState != lifeCycleComponent.getCurrentNetworkState()) {
            System.out.println("{There was a NetworkState change!}");
            run();
        } else {
            run();
        }
    } catch (SocketException | InterruptedException e) {
        e.printStackTrace();
    }
}

public static void main(String[] args) {
    Thread checkingNetworkStates = new Thread(new LifeCycleComponent());
    checkingNetworkStates.start();
}

Mam nadzieję, że to pomoże, nawet jeśli to tylko trochę.

Twoje zdrowie


-1

Byłoby to naprawdę brzydkie, ale możesz owinąć wątek innym wątkiem, aby ponownie uruchomić kod, ale zrób to tylko wtedy, gdy naprawdę musisz.

Musiałem naprawić wyciek zasobów, który został spowodowany przez programistę, który utworzył wątek, ale zamiast go uruchomić, bezpośrednio wywołał metodę run (). Unikaj więc tego, chyba że naprawdę wiesz, jakie skutki uboczne wywołuje.


Ale sugestia nie polegała na run()bezpośrednim wywołaniu , tylko po to, aby osadzić Runnable w wątku i prawdopodobnie wywołać start().
H2ONaCl

@ H2ONaCl Jeśli czytasz cytowany przeze mnie tekst, sugerowano, aby zawinąć wątek w wątek. Być może nie przeczytałeś oryginalnej sugestii, zanim została zredagowana.
Torben
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.